Compare commits
58 Commits
8.2.1
...
zone.js-0.
Author | SHA1 | Date | |
---|---|---|---|
2a6e6c02ed | |||
fa4e17082c | |||
ebb27727e4 | |||
046532b661 | |||
7b9891d7cd | |||
a2183ddb7a | |||
3122f3415a | |||
aaf29c8099 | |||
32e2f4daef | |||
a7c71d1a57 | |||
f2d47c96c4 | |||
184d270725 | |||
a610d12266 | |||
d0d875a3fe | |||
e8b8f6d09b | |||
9e9179e915 | |||
c1ae6124c8 | |||
b3b5c66414 | |||
82b97280f3 | |||
ecffbda664 | |||
adc39752f3 | |||
584b42343f | |||
8e7a0d4ff9 | |||
4db959260b | |||
76503e65c8 | |||
eac993dfce | |||
975917bafd | |||
185b3dd08e | |||
78659ec0b0 | |||
a9ec3db91a | |||
561ec6a5be | |||
c0317d40c9 | |||
a4bc0db474 | |||
430124a051 | |||
e08391b333 | |||
a77d0e22bf | |||
4f42eb4e77 | |||
f216724c2c | |||
5c9a8961da | |||
3479fddf68 | |||
5bebac42f9 | |||
cbcbe23fd1 | |||
fc6f48185c | |||
80f290e301 | |||
5e5be43acd | |||
0386c964b5 | |||
f5c605b608 | |||
14dba72aee | |||
5f0d5e9ccf | |||
5296c04f61 | |||
40a0666651 | |||
4da805243a | |||
14ae50b4c3 | |||
397d0ba9a3 | |||
859ebdd836 | |||
30673090ec | |||
6033446d2d | |||
174770e6f3 |
@ -321,7 +321,7 @@ jobs:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Build aio (with local Angular packages)
|
||||
- run: yarn --cwd aio build-local-ci
|
||||
- run: yarn --cwd aio build-local --progress=false
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
@ -340,7 +340,7 @@ jobs:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Build aio with Ivy (using local Angular packages)
|
||||
- run: yarn --cwd aio build-with-ivy-ci
|
||||
- run: yarn --cwd aio build-with-ivy --progress=false
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
|
94
CHANGELOG.md
94
CHANGELOG.md
@ -1,45 +1,44 @@
|
||||
<a name="8.2.1"></a>
|
||||
## [8.2.1](https://github.com/angular/angular/compare/8.2.0...8.2.1) (2019-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([04ebd59](https://github.com/angular/angular/commit/04ebd59)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
|
||||
<a name="9.0.0-next.0"></a>
|
||||
# [9.0.0-next.0](https://github.com/angular/angular/compare/8.2.0-next.2...9.0.0-next.0) (2019-07-31)
|
||||
|
||||
* Ivy related improvements and fixes
|
||||
|
||||
|
||||
<a name="8.2.0"></a>
|
||||
# [8.2.0](https://github.com/angular/angular/compare/8.2.0-rc.0...8.2.0) (2019-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** DebugElement.listeners not cleared on destroy ([#31820](https://github.com/angular/angular/issues/31820)) ([46b160e](https://github.com/angular/angular/commit/46b160e))
|
||||
|
||||
|
||||
|
||||
<a name="8.2.0-rc.0"></a>
|
||||
# [8.2.0-rc.0](https://github.com/angular/angular/compare/8.2.0-next.2...8.2.0-rc.0) (2019-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** increase memory limit of ngc under bazel from 2 to 4 GB ([#31784](https://github.com/angular/angular/issues/31784)) ([5a8eb92](https://github.com/angular/angular/commit/5a8eb92))
|
||||
* **core:** allow Z variations of CSS transforms in sanitizer ([#29264](https://github.com/angular/angular/issues/29264)) ([78e7fdd](https://github.com/angular/angular/commit/78e7fdd))
|
||||
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([7151eae](https://github.com/angular/angular/commit/7151eae)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
|
||||
* **platform-browser:** debug element query predicates not compatible with strictFunctionTypes ([#30993](https://github.com/angular/angular/issues/30993)) ([10a1e19](https://github.com/angular/angular/commit/10a1e19))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db]
|
||||
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
|
||||
* **bazel:** compile targets used for indexing by Kythe with Ivy ([#31786](https://github.com/angular/angular/issues/31786)) ([82055b2](https://github.com/angular/angular/commit/82055b2))
|
||||
* **upgrade:** support $element in upgraded component template/templateUrl functions ([#31637](https://github.com/angular/angular/issues/31637)) ([29e1c53](https://github.com/angular/angular/commit/29e1c53))
|
||||
* **bazel:** allow passing a custom bazel compiler host to ngc compile ([#31341](https://github.com/angular/angular/issues/31341)) ([a29dc96](https://github.com/angular/angular/commit/a29dc96))
|
||||
* **bazel:** allow passing and rewriting an old bazel host ([#31381](https://github.com/angular/angular/issues/31381)) ([11a208f](https://github.com/angular/angular/commit/11a208f)), closes [#31341](https://github.com/angular/angular/issues/31341)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([24ca582](https://github.com/angular/angular/commit/24ca582)), closes [#31627](https://github.com/angular/angular/issues/31627)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** DebugElement.listeners not cleared on destroy ([#31820](https://github.com/angular/angular/issues/31820)) ([46b160e](https://github.com/angular/angular/commit/46b160e))
|
||||
* **bazel:** increase memory limit of ngc under bazel from 2 to 4 GB ([#31784](https://github.com/angular/angular/issues/31784)) ([5a8eb92](https://github.com/angular/angular/commit/5a8eb92))
|
||||
* **core:** allow Z variations of CSS transforms in sanitizer ([#29264](https://github.com/angular/angular/issues/29264)) ([78e7fdd](https://github.com/angular/angular/commit/78e7fdd))
|
||||
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([7151eae](https://github.com/angular/angular/commit/7151eae)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
|
||||
* **platform-browser:** debug element query predicates not compatible with strictFunctionTypes ([#30993](https://github.com/angular/angular/issues/30993)) ([10a1e19](https://github.com/angular/angular/commit/10a1e19))
|
||||
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([9204de9](https://github.com/angular/angular/commit/9204de9))
|
||||
* **bazel:** pass custom bazel compiler host rather than rewriting one ([#31496](https://github.com/angular/angular/issues/31496)) ([0c61a35](https://github.com/angular/angular/commit/0c61a35))
|
||||
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([3166cff](https://github.com/angular/angular/commit/3166cff)), closes [#22524](https://github.com/angular/angular/issues/22524)
|
||||
* **language-service:** Eagarly initialize data members ([#31577](https://github.com/angular/angular/issues/31577)) ([0110de2](https://github.com/angular/angular/commit/0110de2))
|
||||
* **bazel:** revert location of xi18n outputs to bazel-genfiles ([#31410](https://github.com/angular/angular/issues/31410)) ([1d3e227](https://github.com/angular/angular/commit/1d3e227))
|
||||
* **compiler:** give ASTWithSource its own visit method ([#31347](https://github.com/angular/angular/issues/31347)) ([6aaca21](https://github.com/angular/angular/commit/6aaca21))
|
||||
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
|
||||
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([d7be38f](https://github.com/angular/angular/commit/d7be38f)), closes [#30968](https://github.com/angular/angular/issues/30968)
|
||||
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([93abc35](https://github.com/angular/angular/commit/93abc35))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.3"></a>
|
||||
@ -56,17 +55,6 @@
|
||||
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([1f3daa0](https://github.com/angular/angular/commit/1f3daa0)), closes [#31627](https://github.com/angular/angular/issues/31627)
|
||||
|
||||
|
||||
<a name="8.2.0-next.2"></a>
|
||||
# [8.2.0-next.2](https://github.com/angular/angular/compare/8.2.0-next.1...8.2.0-next.2) (2019-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([9204de9](https://github.com/angular/angular/commit/9204de9))
|
||||
* **bazel:** pass custom bazel compiler host rather than rewriting one ([#31496](https://github.com/angular/angular/issues/31496)) ([0c61a35](https://github.com/angular/angular/commit/0c61a35))
|
||||
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([3166cff](https://github.com/angular/angular/commit/3166cff)), closes [#22524](https://github.com/angular/angular/issues/22524)
|
||||
* **language-service:** Eagarly initialize data members ([#31577](https://github.com/angular/angular/issues/31577)) ([0110de2](https://github.com/angular/angular/commit/0110de2))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.2"></a>
|
||||
@ -80,23 +68,6 @@
|
||||
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
|
||||
|
||||
|
||||
|
||||
<a name="8.2.0-next.1"></a>
|
||||
# [8.2.0-next.1](https://github.com/angular/angular/compare/8.2.0-next.0...8.2.0-next.1) (2019-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** revert location of xi18n outputs to bazel-genfiles ([#31410](https://github.com/angular/angular/issues/31410)) ([1d3e227](https://github.com/angular/angular/commit/1d3e227))
|
||||
* **compiler:** give ASTWithSource its own visit method ([#31347](https://github.com/angular/angular/issues/31347)) ([6aaca21](https://github.com/angular/angular/commit/6aaca21))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.1"></a>
|
||||
## [8.1.1](https://github.com/angular/angular/compare/8.1.0...8.1.1) (2019-07-10)
|
||||
|
||||
@ -106,23 +77,6 @@
|
||||
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
|
||||
|
||||
|
||||
<a name="8.2.0-next.0"></a>
|
||||
# [8.2.0-next.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.2.0-next.0) (2019-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
|
||||
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([d7be38f](https://github.com/angular/angular/commit/d7be38f)), closes [#30968](https://github.com/angular/angular/issues/30968)
|
||||
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([93abc35](https://github.com/angular/angular/commit/93abc35))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** allow passing a custom bazel compiler host to ngc compile ([#31341](https://github.com/angular/angular/issues/31341)) ([a29dc96](https://github.com/angular/angular/commit/a29dc96))
|
||||
* **bazel:** allow passing and rewriting an old bazel host ([#31381](https://github.com/angular/angular/issues/31381)) ([11a208f](https://github.com/angular/angular/commit/11a208f)), closes [#31341](https://github.com/angular/angular/issues/31341)
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0"></a>
|
||||
# [8.1.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.1.0) (2019-07-02)
|
||||
|
@ -14,12 +14,10 @@ Here are the most important tasks you might need to use:
|
||||
|
||||
* `yarn` - install all the dependencies.
|
||||
* `yarn setup` - install all the dependencies, boilerplate, stackblitz, zips and run dgeni on the docs.
|
||||
* `yarn setup-local` - same as `setup`, but build the Angular packages from the source code and use these locally built versions (instead of the ones fetched from npm) for aio and docs examples boilerplate.
|
||||
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
|
||||
|
||||
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
||||
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
||||
* `yarn build-with-ivy` - same as `build-local`, but in addition also turns on `ivy` mode in aio.
|
||||
(Note: To turn on `ivy` mode in examples, see `yarn boilerplate:add` below.)
|
||||
|
||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||
@ -33,10 +31,7 @@ Here are the most important tasks you might need to use:
|
||||
* `yarn docs-lint` - check that the doc gen code follows our style rules.
|
||||
* `yarn docs-test` - run the unit tests for the doc generation code.
|
||||
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
|
||||
- Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||
- Add the option `--ivy` to turn on `ivy` mode.
|
||||
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
||||
* `yarn generate-stackblitz` - generate the stackblitz files that are used by the `live-example` tags in the docs.
|
||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||
|
12
aio/content/examples/setup/quickstart-specs.stackblitz.json
Normal file
12
aio/content/examples/setup/quickstart-specs.stackblitz.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Quickstart AppComponent Testing",
|
||||
"files":[
|
||||
"src/browser-test-shim.js",
|
||||
"src/app/app.component.ts",
|
||||
"src/app/app.component.spec.ts",
|
||||
"src/quickstart-specs.html"
|
||||
],
|
||||
"main": "src/quickstart-specs.html",
|
||||
"file": "src/app/app.component.spec.ts",
|
||||
"tags": ["quickstart", "setup", "testing"]
|
||||
}
|
36
aio/content/examples/setup/src/quickstart-specs.html
Normal file
36
aio/content/examples/setup/src/quickstart-specs.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!-- Run application specs in a browser -->
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>1st Specs</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone-testing.js"></script>
|
||||
|
||||
<!-- #docregion files -->
|
||||
<script>
|
||||
var __spec_files__ = [
|
||||
'app/app.component.spec'
|
||||
];
|
||||
</script>
|
||||
<!-- #enddocregion files-->
|
||||
<script src="browser-test-shim.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
12
aio/content/examples/setup/stackblitz.json
Normal file
12
aio/content/examples/setup/stackblitz.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "QuickStart Setup",
|
||||
"files": [
|
||||
"src/app/app.component.ts",
|
||||
"src/app/app.module.ts",
|
||||
"src/index.html",
|
||||
"src/main.ts",
|
||||
"src/styles.css"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["quickstart", "setup", "seed"]
|
||||
}
|
@ -1,22 +1,24 @@
|
||||
{
|
||||
"description": "Testing - specs",
|
||||
"files":[
|
||||
"src/expected.ts",
|
||||
"src/index-specs.html",
|
||||
"src/main-specs.ts",
|
||||
"src/styles.css",
|
||||
"src/test.css",
|
||||
"src/tests.sb.ts",
|
||||
|
||||
"e2e/src/**/*.ts",
|
||||
|
||||
"src/app/**/*.css",
|
||||
"src/app/**/*.html",
|
||||
"src/app/**/*.ts",
|
||||
"src/app/**/*.spec.ts",
|
||||
|
||||
"src/testing/**/*",
|
||||
"src/testing/*.ts",
|
||||
|
||||
"src/**/*.spec.ts"
|
||||
"!src/main.ts",
|
||||
"!src/app/bag/*.*",
|
||||
"!src/app/1st.spec.ts",
|
||||
|
||||
"src/expected.ts",
|
||||
"src/test.css",
|
||||
"src/tests.sb.ts",
|
||||
"src/main-specs.ts",
|
||||
"src/index-specs.html"
|
||||
],
|
||||
"main": "src/index-specs.html",
|
||||
"tags": ["testing"]
|
||||
|
@ -1,3 +1,3 @@
|
||||
import jasmineRequire from 'jasmine-core/lib/jasmine-core/jasmine.js';
|
||||
const jasmineRequire = require('jasmine-core/lib/jasmine-core/jasmine.js');
|
||||
|
||||
window['jasmineRequire'] = jasmineRequire;
|
||||
|
@ -1,18 +1,19 @@
|
||||
{
|
||||
"description": "Heroes Test App",
|
||||
"files":[
|
||||
"src/index.html",
|
||||
"src/main.ts",
|
||||
"src/styles.css",
|
||||
"src/test.css",
|
||||
|
||||
"e2e/src/**/*.ts",
|
||||
|
||||
"src/app/**/*.css",
|
||||
"src/app/**/*.html",
|
||||
"src/app/**/*.ts",
|
||||
|
||||
"!src/**/*.spec.ts"
|
||||
"!src/app/bag/*.*",
|
||||
|
||||
"!src/test.ts",
|
||||
|
||||
"src/test.css",
|
||||
"src/main.ts",
|
||||
"src/index.html"
|
||||
],
|
||||
"tags": ["testing"]
|
||||
}
|
||||
|
@ -1,148 +0,0 @@
|
||||
# Angular compiler options
|
||||
|
||||
When you use [AOT compilation](guide/aot-compiler), you can control how your application is compiled by specifying *template* compiler options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration).
|
||||
|
||||
The template options object, `angularCompilerOptions`, is a sibling to the `compilerOptions` object that supplies standard options to the TypeScript compiler.
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
...
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"preserveWhitespaces": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
This page describes the available Angular template compiler options.
|
||||
|
||||
### `allowEmptyCodegenFiles`
|
||||
|
||||
When true, generate all possible files even if they are empty. Default is false. Used by the Bazel build rules to simplify how Bazel rules track file dependencies. Do not use this option outside of the Bazel rules.
|
||||
|
||||
### `annotationsAs`
|
||||
|
||||
Modifies how Angular-specific annotations are emitted to improve tree-shaking. Non-Angular annotations are not affected. One of `static fields` (the default) or `decorators`.
|
||||
|
||||
* By default, the compiler replaces decorators with a static field in the class, which allows advanced tree-shakers like [Closure compiler](https://github.com/google/closure-compiler) to remove unused classes.
|
||||
|
||||
* The `decorators` value leaves the decorators in place, which makes compilation faster. TypeScript emits calls to the` __decorate` helper. Use `--emitDecoratorMetadata` for runtime reflection (but note taht the resulting code will not properly tree-shake.
|
||||
|
||||
### `annotateForClosureCompiler`
|
||||
|
||||
When true, use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
|
||||
[Closure Compiler](https://github.com/google/closure-compiler). Default is false.
|
||||
|
||||
### `disableExpressionLowering`
|
||||
|
||||
When true (the default), transforms code that is or could be used in an annotation, to allow it to be imported from template factory modules. See [metadata rewriting](guide/aot-compiler#metadata-rewriting) for more information.
|
||||
|
||||
When `false`, disables this rewriting, requiring the rewriting to be done manually.
|
||||
|
||||
### `disableTypeScriptVersionCheck`
|
||||
|
||||
When `true`, the compiler does not check the TypeScript version and does not report an error when an unsupported version of TypeScript is used. Not recommended, as unsupported versions of TypeScript might have undefined behavior. Default is false.
|
||||
|
||||
### `enableResourceInlining`
|
||||
|
||||
When true, replaces the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
|
||||
|
||||
When enabled, the `.js` output of `ngc` does not include any lazy-loaded template or style URLs.
|
||||
|
||||
|
||||
{@a enablelegacytemplate}
|
||||
|
||||
### `enableLegacyTemplate`
|
||||
|
||||
When true, enables use of the `<template>` element, which was deprecated in Angular 4.0, in favor of `<ng-template>` (to avoid colliding with the DOM's element of the same name). Default is false. Might be required by some third-party Angular libraries. |
|
||||
|
||||
### `flatModuleId`
|
||||
|
||||
The module ID to use for importing a flat module (when `flatModuleOutFile` is true). References generated by the template compiler use this module name when importing symbols
|
||||
from the flat module. Ignored if `flatModuleOutFile` is false.
|
||||
|
||||
### `flatModuleOutFile`
|
||||
|
||||
When true, generates a flat module index of the given file name and the corresponding flat module metadata. Use to create flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option is used, the `package.json` for the library should refer
|
||||
to the generated flat module index instead of the library index file.
|
||||
|
||||
Produces only one `.metadata.json` file, which contains all the metadata necessary
|
||||
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
|
||||
module index is used to import symbols that includes both the public API from the library index
|
||||
as well as shrowded internal symbols.
|
||||
|
||||
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
|
||||
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
|
||||
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced.
|
||||
|
||||
A flat module index `.d.ts` and `.js` is created with the given `flatModuleOutFile` name in the same location as the library index `.d.ts` file.
|
||||
|
||||
For example, if a library uses the `public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field would be `["public_api.ts"]`.
|
||||
The `flatModuleOutFile` options could then be set to (for example) `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files.
|
||||
The `module` field of the library's `package.json` would be `"index.js"` and the `typings` field
|
||||
would be `"index.d.ts"`.
|
||||
|
||||
### `fullTemplateTypeCheck`
|
||||
|
||||
When true (recommended), enables the [binding expression validation](guide/aot-compiler#binding-expression-validation) phase of the template compiler, which uses TypeScript to validate binding expressions.
|
||||
|
||||
Default is currently false.
|
||||
|
||||
### `generateCodeForLibraries`
|
||||
|
||||
When true (the default), generates factory files (`.ngfactory.js` and `.ngstyle.js`)
|
||||
for `.d.ts` files with a corresponding `.metadata.json` file.
|
||||
|
||||
When false, factory files are generated only for `.ts` files. Do this when using factory summaries.
|
||||
|
||||
|
||||
### `preserveWhitespaces`
|
||||
|
||||
When false (the default), removes blank text nodes from compiled templates, which results in smaller emitted template factory modules. Set to true to preserve blank text nodes.
|
||||
|
||||
### `skipMetadataEmit`
|
||||
|
||||
When true, does not to produce `.metadata.json` files. Default is `false`.
|
||||
|
||||
The `.metadata.json` files contain information needed by the template compiler from a `.ts`
|
||||
file that is not included in the `.d.ts` file produced by the TypeScript compiler.
|
||||
This information includes, for example, the content of annotations (such as a component's template), which TypeScript emits to the `.js` file but not to the `.d.ts` file.
|
||||
|
||||
You can set to `true` when using factory summaries, because the factory summaries
|
||||
include a copy of the information that is in the `.metadata.json` file.
|
||||
|
||||
Set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
|
||||
are not valid for this style of TypeScript output. However, we do not recommend using `--outFile` with Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
|
||||
|
||||
### `skipTemplateCodegen`
|
||||
|
||||
When true, does not emit `.ngfactory.js` and `.ngstyle.js` files. This turns off most of the template compiler and disables the reporting of template diagnostics.
|
||||
|
||||
Can be used to instruct the template compiler to produce `.metadata.json` files for distribution with an `npm` package while avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to `npm`.
|
||||
|
||||
### `strictMetadataEmit`
|
||||
|
||||
When true, reports an error to the `.metadata.json` file if `"skipMetadataEmit"` is `false`.
|
||||
Default is false. Use only when `"skipMetadataEmit"` is false and `"skipTemplateCodeGen"` is true.
|
||||
|
||||
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
|
||||
|
||||
It is valid for `.metadata.json` files to contain errors.
|
||||
The template compiler reports these errors if the metadata is used to determine the contents of an annotation.
|
||||
The metadata collector cannot predict the symbols that are designed for use in an annotation, so it preemptively includes error nodes in the metadata for the exported symbols.
|
||||
The template compiler can then use the error nodes to report an error if these symbols are used.
|
||||
|
||||
If the client of a library intends to use a symbol in an annotation, the template compiler does not normally report this until the client uses the symbol.
|
||||
This option allows detection of these errors during the build phase of
|
||||
the library and is used, for example, in producing Angular libraries themselves.
|
||||
|
||||
### `strictInjectionParameters`
|
||||
|
||||
When true (recommended), reports an error for a supplied parameter whose injection type cannot be determined. When false (currently the default), constructor parameters of classes marked with `@Injectable` whose type cannot be resolved produce a warning.
|
||||
|
||||
### `trace`
|
||||
|
||||
When true, prints extra information while compiling templates. Default is false.
|
@ -79,9 +79,11 @@ there are fewer opportunities for injection attacks.
|
||||
|
||||
When you use the Angular AOT compiler, you can control your app compilation in two ways:
|
||||
|
||||
* By [specifying Angular metadata](#metadata-aot), as described below.
|
||||
* By providing template compiler options in the `tsconfig.json` file.
|
||||
|
||||
* By providing options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration). See [Angular compiler options](guide/angular-compiler-options).
|
||||
For more information, see [Angular template compiler options](#compiler-options).
|
||||
|
||||
* By [specifying Angular metadata](#metadata-aot).
|
||||
|
||||
|
||||
{@a metadata-aot}
|
||||
@ -1163,7 +1165,7 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](
|
||||
In the validation phase, the Angular template compiler uses the TypeScript compiler to validate the
|
||||
binding expressions in templates. Enable this phase explicitly by adding the compiler
|
||||
option `"fullTemplateTypeCheck"` in the `"angularCompilerOptions"` of the project's `tsconfig.json` (see
|
||||
[Angular Compiler Options](guide/angular-compiler-options)).
|
||||
[Angular Compiler Options](#compiler-options)).
|
||||
|
||||
Template validation produces error messages when a type error is detected in a template binding
|
||||
expression, similar to how type errors are reported by the TypeScript compiler against code in a `.ts`
|
||||
@ -1327,3 +1329,198 @@ Similar to TypeScript Compiler, Angular Compiler also supports `extends` in the
|
||||
}
|
||||
```
|
||||
More information about tsconfig extends can be found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
|
||||
|
||||
{@a compiler-options}
|
||||
## Angular template compiler options
|
||||
|
||||
The template compiler options are specified as members of the `"angularCompilerOptions"` object in the `tsconfig.json` file. Specify template compiler options along with the options supplied to the TypeScript compiler as shown here:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
...
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"preserveWhitespaces": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following section describes the Angular's template compiler options.
|
||||
|
||||
### *enableResourceInlining*
|
||||
This option instructs the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
|
||||
When enabled, the `.js` output of `ngc` will have no lazy-loaded `templateUrl` or `styleUrls`.
|
||||
|
||||
### *skipMetadataEmit*
|
||||
|
||||
This option tells the compiler not to produce `.metadata.json` files.
|
||||
The option is `false` by default.
|
||||
|
||||
`.metadata.json` files contain information needed by the template compiler from a `.ts`
|
||||
file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information contains,
|
||||
for example, the content of annotations (such as a component's template), which TypeScript
|
||||
emits to the `.js` file but not to the `.d.ts` file.
|
||||
|
||||
This option should be set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
|
||||
are not valid for this style of TypeScript output. It is not recommended to use `--outFile` with
|
||||
Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
|
||||
|
||||
This option can also be set to `true` when using factory summaries because the factory summaries
|
||||
include a copy of the information that is in the `.metadata.json` file.
|
||||
|
||||
### *strictMetadataEmit*
|
||||
|
||||
This option tells the template compiler to report an error to the `.metadata.json`
|
||||
file if `"skipMetadataEmit"` is `false`. This option is `false` by default. This should only be used when `"skipMetadataEmit"` is `false` and `"skipTemplateCodeGen"` is `true`.
|
||||
|
||||
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
|
||||
|
||||
It is valid for `.metadata.json` files to contain errors. The template compiler reports these errors
|
||||
if the metadata is used to determine the contents of an annotation. The metadata
|
||||
collector cannot predict the symbols that are designed for use in an annotation, so it will preemptively
|
||||
include error nodes in the metadata for the exported symbols. The template compiler can then use the error
|
||||
nodes to report an error if these symbols are used. If the client of a library intends to use a symbol in an annotation, the template compiler will not normally report
|
||||
this until the client uses the symbol. This option allows detecting these errors during the build phase of
|
||||
the library and is used, for example, in producing Angular libraries themselves.
|
||||
|
||||
### *skipTemplateCodegen*
|
||||
|
||||
This option tells the compiler to suppress emitting `.ngfactory.js` and `.ngstyle.js` files. When set,
|
||||
this turns off most of the template compiler and disables reporting template diagnostics.
|
||||
This option can be used to instruct the
|
||||
template compiler to produce `.metadata.json` files for distribution with an `npm` package while
|
||||
avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to
|
||||
`npm`.
|
||||
|
||||
### *strictInjectionParameters*
|
||||
|
||||
When set to `true`, this options tells the compiler to report an error for a parameter supplied
|
||||
whose injection type cannot be determined. When this option is not provided or is `false`, constructor parameters of classes marked with `@Injectable` whose type cannot be resolved will
|
||||
produce a warning.
|
||||
|
||||
*Note*: It is recommended to change this option explicitly to `true` as this option will default to `true` in the future.
|
||||
|
||||
### *flatModuleOutFile*
|
||||
|
||||
When set to `true`, this option tells the template compiler to generate a flat module
|
||||
index of the given file name and the corresponding flat module metadata. Use this option when creating
|
||||
flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option
|
||||
is used, the `package.json` for the library should refer
|
||||
to the generated flat module index instead of the library index file. With this
|
||||
option only one `.metadata.json` file is produced, which contains all the metadata necessary
|
||||
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
|
||||
module index is used to import symbols that includes both the public API from the library index
|
||||
as well as shrowded internal symbols.
|
||||
|
||||
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
|
||||
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
|
||||
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced. A flat module
|
||||
index `.d.ts` and `.js` will be created with the given `flatModuleOutFile` name in the same
|
||||
location as the library index `.d.ts` file. For example, if a library uses the
|
||||
`public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field
|
||||
would be `["public_api.ts"]`. The `flatModuleOutFile` options could then be set to, for
|
||||
example `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files. The
|
||||
library's `package.json`'s `module` field would be `"index.js"` and the `typings` field
|
||||
would be `"index.d.ts"`.
|
||||
|
||||
### *flatModuleId*
|
||||
|
||||
This option specifies the preferred module id to use for importing a flat module.
|
||||
References generated by the template compiler will use this module name when importing symbols
|
||||
from the flat module.
|
||||
This is only meaningful when `flatModuleOutFile` is also supplied. Otherwise the compiler ignores
|
||||
this option.
|
||||
|
||||
### *generateCodeForLibraries*
|
||||
|
||||
This option tells the template compiler to generate factory files (`.ngfactory.js` and `.ngstyle.js`)
|
||||
for `.d.ts` files with a corresponding `.metadata.json` file. This option defaults to
|
||||
`true`. When this option is `false`, factory files are generated only for `.ts` files.
|
||||
|
||||
This option should be set to `false` when using factory summaries.
|
||||
|
||||
### *fullTemplateTypeCheck*
|
||||
|
||||
This option tells the compiler to enable the [binding expression validation](#binding-expression-validation)
|
||||
phase of the template compiler which uses TypeScript to validate binding expressions.
|
||||
|
||||
This option is `false` by default.
|
||||
|
||||
*Note*: It is recommended to set this to `true` because this option will default to `true` in the future.
|
||||
|
||||
### *annotateForClosureCompiler*
|
||||
|
||||
This option tells the compiler to use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted
|
||||
JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
|
||||
[Closure Compiler](https://github.com/google/closure-compiler). This option defaults to `false`.
|
||||
|
||||
### *annotationsAs*
|
||||
|
||||
Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular
|
||||
annotations and decorators are unaffected. Default is `static fields`.
|
||||
|
||||
<style>
|
||||
td, th {vertical-align: top}
|
||||
</style>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>decorators</code></td>
|
||||
<td>Leave the decorators in place. This makes compilation faster. TypeScript will emit calls to the __decorate helper. Use <code>--emitDecoratorMetadata</code> for runtime reflection. However, the resulting code will not properly tree-shake.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>static fields</code></td>
|
||||
<td>Replace decorators with a static field in the class. Allows advanced tree-shakers like
|
||||
<a href="https://github.com/google/closure-compiler">Closure compiler</a> to remove unused classes.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
### *trace*
|
||||
|
||||
This tells the compiler to print extra information while compiling templates.
|
||||
|
||||
### *enableLegacyTemplate*
|
||||
|
||||
Use of the `<template>` element was deprecated starting in Angular 4.0 in favor of using
|
||||
`<ng-template>` to avoid colliding with the DOM's element of the same name. Setting this option to
|
||||
`true` enables the use of the deprecated `<template>` element. This option
|
||||
is `false` by default. This option might be required by some third-party Angular libraries.
|
||||
|
||||
### *disableExpressionLowering*
|
||||
|
||||
The Angular template compiler transforms code that is used, or could be used, in an annotation
|
||||
to allow it to be imported from template factory modules. See
|
||||
[metadata rewriting](#metadata-rewriting) for more information.
|
||||
|
||||
Setting this option to `false` disables this rewriting, requiring the rewriting to be
|
||||
done manually.
|
||||
|
||||
### *disableTypeScriptVersionCheck*
|
||||
|
||||
When `true`, this option tells the compiler not to check the TypeScript version.
|
||||
The compiler will skip checking and will not error out when an unsupported version of TypeScript is used.
|
||||
Setting this option to `true` is not recommended because unsupported versions of TypeScript might have undefined behavior.
|
||||
|
||||
This option is `false` by default.
|
||||
|
||||
### *preserveWhitespaces*
|
||||
|
||||
This option tells the compiler whether to remove blank text nodes from compiled templates.
|
||||
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
|
||||
|
||||
### *allowEmptyCodegenFiles*
|
||||
|
||||
Tells the compiler to generate all the possible generated files even if they are empty. This option is
|
||||
`false` by default. This is an option used by the Bazel build rules and is needed to simplify
|
||||
how Bazel rules track file dependencies. It is not recommended to use this option outside of the Bazel
|
||||
rules.
|
||||
|
||||
|
@ -53,7 +53,7 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
IE
|
||||
</td>
|
||||
<td>
|
||||
11, 10, 9 ("compatibility view" mode not supported)
|
||||
11, 10, 9
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -183,7 +183,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
<td>
|
||||
Chrome, Firefox, Edge, <br>
|
||||
Safari, Android, IE 10+
|
||||
Safari, Android, IE10+
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -197,7 +197,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
IE 9
|
||||
IE9
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -275,7 +275,7 @@ Some features of Angular may require additional polyfills.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
All but Chrome, Firefox, Edge, IE 11 and Safari 10
|
||||
All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -294,7 +294,7 @@ Some features of Angular may require additional polyfills.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
IE 10, IE 11
|
||||
IE10, IE11
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
@ -178,7 +178,7 @@ For more information, see [/deep/, >>>, and ::ng-deep](guide/component-styles#de
|
||||
{@a template-tag}
|
||||
### <template> tag
|
||||
|
||||
The `<template>` tag was deprecated in v4 to avoid colliding with the DOM's element of the same name (such as when using web components). Use `<ng-template>` instead. For more information, see the [Ahead-of-Time Compilation](guide/angular-compiler-options#enablelegacytemplate) guide.
|
||||
The `<template>` tag was deprecated in v4 to avoid colliding with the DOM's element of the same name (such as when using web components). Use `<ng-template>` instead. For more information, see the [Ahead-of-Time Compilation](guide/aot-compiler#enablelegacytemplate) guide.
|
||||
|
||||
|
||||
|
||||
|
@ -2,17 +2,15 @@
|
||||
|
||||
Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example:
|
||||
|
||||
* You can define [custom events](guide/template-syntax#custom-events-with-eventemitter) that send observable output data from a child to a parent component.
|
||||
* The `EventEmitter` class extends `Observable`.
|
||||
* The HTTP module uses observables to handle AJAX requests and responses.
|
||||
* The Router and Forms modules use observables to listen for and respond to user-input events.
|
||||
|
||||
## Transmitting data between components
|
||||
## Event emitter
|
||||
|
||||
Angular provides an `EventEmitter` class that is used when publishing values from a component through the [`@Output()` decorator](guide/template-syntax#how-to-use-output).
|
||||
`EventEmitter` extends [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject), adding an `emit()` method so it can send arbitrary values.
|
||||
When you call `emit()`, it passes the emitted value to the `next()` method of any subscribed observer.
|
||||
Angular provides an `EventEmitter` class that is used when publishing values from a component through the `@Output()` decorator. `EventEmitter` extends `Observable`, adding an `emit()` method so it can send arbitrary values. When you call `emit()`, it passes the emitted value to the `next()` method of any subscribed observer.
|
||||
|
||||
A good example of usage can be found in the [EventEmitter](https://angular.io/api/core/EventEmitter) documentation. Here is the example component that listens for open and close events:
|
||||
A good example of usage can be found on the [EventEmitter](https://angular.io/api/core/EventEmitter) documentation. Here is the example component that listens for open and close events:
|
||||
|
||||
`<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>`
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# The RxJS library
|
||||
|
||||
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change ([Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code. See ([RxJS Docs](https://rxjs.dev/guide/overview)).
|
||||
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change ([Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code ([RxJS Docs](http://reactivex.io/rxjs/)).
|
||||
|
||||
RxJS provides an implementation of the `Observable` type, which is needed until the type becomes part of the language and until browsers support it. The library also provides utility functions for creating and working with observables. These utility functions can be used for:
|
||||
|
||||
@ -45,7 +45,7 @@ The `pipe()` function is also a method on the RxJS `Observable`, so you use this
|
||||
|
||||
### Common operators
|
||||
|
||||
RxJS provides many operators, but only a handful are used frequently. For a list of operators and usage samples, visit the [RxJS API Documentation](https://rxjs.dev/api).
|
||||
RxJS provides many operators, but only a handful are used frequently. For a list of operators and usage samples, visit the [RxJS API Documentation](https://rxjs-dev.firebaseapp.com/api).
|
||||
|
||||
<div class="alert is-helpful">
|
||||
Note that, for Angular apps, we prefer combining operators with pipes, rather than chaining. Chaining is used in many RxJS examples.
|
||||
|
@ -927,7 +927,7 @@ As always, strive for consistency.
|
||||
<div class="s-rule do">
|
||||
|
||||
**Do** use a custom prefix for a component selector.
|
||||
For example, the prefix `toh` represents **T**our **o**f **H**eroes and the prefix `admin` represents an admin feature area.
|
||||
For example, the prefix `toh` represents from **T**our **o**f **H**eroes and the prefix `admin` represents an admin feature area.
|
||||
|
||||
</div>
|
||||
|
||||
@ -1670,7 +1670,7 @@ keep the **F**lattest structure you can, and
|
||||
|
||||
|
||||
|
||||
**Why?** LIFT provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly.
|
||||
**Why?** LIFT Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly.
|
||||
To confirm your intuition about a particular structure, ask:
|
||||
_can I quickly open and start work in all of the related files for this feature_?
|
||||
|
||||
@ -1690,7 +1690,7 @@ _can I quickly open and start work in all of the related files for this feature_
|
||||
|
||||
|
||||
|
||||
**Do** make locating code intuitive, simple, and fast.
|
||||
**Do** make locating code intuitive, simple and fast.
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -6,14 +6,14 @@ This guide offers tips and techniques for unit and integration testing Angular a
|
||||
The guide presents tests of a sample application created with the [Angular CLI](cli). This sample application is much like the one created in the [_Tour of Heroes_ tutorial](tutorial).
|
||||
The sample application and all tests in this guide are available for inspection and experimentation:
|
||||
|
||||
- <live-example embedded-style noDownload>Sample app</live-example>
|
||||
- <live-example stackblitz="specs" noDownload>Tests</live-example>
|
||||
- <live-example embedded-style>Sample app</live-example>
|
||||
- <live-example stackblitz="specs">Tests</live-example>
|
||||
|
||||
<hr>
|
||||
|
||||
## Setup
|
||||
|
||||
The Angular CLI downloads and installs everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
|
||||
The Angular CLI downloads and install everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
|
||||
|
||||
The project you create with the CLI is immediately ready to test.
|
||||
Just run the [`ng test`](cli/test) CLI command:
|
||||
@ -116,7 +116,7 @@ jobs:
|
||||
build:
|
||||
working_directory: ~/my-project
|
||||
docker:
|
||||
- image: circleci/node:10-browsers
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
|
@ -348,7 +348,7 @@ For example:
|
||||
|
||||
<code-example language="json">
|
||||
|
||||
"sourceMap": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
|
||||
"sourceMaps": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -597,11 +597,6 @@
|
||||
"title": "Ahead-of-Time Compilation",
|
||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
||||
},
|
||||
{
|
||||
"url": "guide/angular-compiler-options",
|
||||
"title": "Compiler Options",
|
||||
"tooltip": "Configuration options for the AOT compiler."
|
||||
},
|
||||
{
|
||||
"url": "guide/build",
|
||||
"title": "Building & Serving",
|
||||
|
@ -91,6 +91,8 @@ configures it with the `routes` in one step by calling
|
||||
|
||||
Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app.
|
||||
|
||||
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
|
||||
</code-example>
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"preinstall": "node ../tools/yarn/check-yarn.js",
|
||||
"postinstall": "node tools/cli-patches/patch.js",
|
||||
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --force --build-packages",
|
||||
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug",
|
||||
"aio-use-npm": "node tools/ng-packages-installer restore .",
|
||||
"aio-check-local": "node tools/ng-packages-installer check .",
|
||||
"ng": "yarn check-env && ng",
|
||||
@ -17,22 +17,18 @@
|
||||
"build": "yarn ~~build",
|
||||
"prebuild-local": "yarn setup-local",
|
||||
"build-local": "yarn ~~build",
|
||||
"prebuild-local-ci": "yarn setup-local --no-build-packages",
|
||||
"build-local-ci": "yarn ~~build --progress=false",
|
||||
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
|
||||
"build-with-ivy": "yarn ~~build",
|
||||
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
|
||||
"build-with-ivy-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js de49294bf",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 403bcb01c",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
"e2e": "ng e2e --no-webdriver-update",
|
||||
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn ~~clean-generated && yarn boilerplate:remove",
|
||||
"setup": "yarn example-use-npm && yarn aio-use-npm",
|
||||
"setup": "yarn aio-use-npm && yarn example-use-npm",
|
||||
"postsetup": "yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
|
||||
"presetup-local": "yarn presetup",
|
||||
"setup-local": "yarn example-use-local && yarn aio-use-local",
|
||||
"setup-local": "yarn aio-use-local && yarn example-use-local",
|
||||
"postsetup-local": "yarn postsetup",
|
||||
"set-opensearch-url": "node --eval \"const sh = require('shelljs'); sh.set('-e'); sh.sed('-i', /PLACEHOLDER_URL/g, process.argv[1], 'dist/assets/opensearch.xml');\"",
|
||||
"presmoke-tests": "yarn update-webdriver",
|
||||
@ -43,7 +39,7 @@
|
||||
"test-pwa-score-localhost": "run-p --race \"~~light-server -s dist -p 4200 --quiet\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
|
||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||
"example-lint": "tslint --config \"content/examples/tslint.json\" \"content/examples/**/*.ts\" --exclude \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug --force",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
|
||||
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
|
||||
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
|
||||
"deploy-production": "scripts/deploy-to-firebase.sh",
|
||||
|
@ -28,8 +28,8 @@
|
||||
"uncompressed": {
|
||||
"runtime-es5": 2932,
|
||||
"runtime-es2015": 2938,
|
||||
"main-es5": 555102,
|
||||
"main-es2015": 572938,
|
||||
"main-es5": 560811,
|
||||
"main-es2015": 499846,
|
||||
"polyfills-es5": 129161,
|
||||
"polyfills-es2015": 53295
|
||||
}
|
||||
|
@ -9,4 +9,5 @@
|
||||
@import 'marketing-layout';
|
||||
@import 'not-found';
|
||||
@import 'sidenav';
|
||||
@import 'table-of-contents';
|
||||
@import 'top-menu';
|
||||
|
10
aio/src/styles/1-layouts/_table-of-contents.scss
Normal file
10
aio/src/styles/1-layouts/_table-of-contents.scss
Normal file
@ -0,0 +1,10 @@
|
||||
nav#main-table-of-contents {
|
||||
width: 200px;
|
||||
height: 900px;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 50px;
|
||||
bottom: 100px;
|
||||
margin-left: 32px;
|
||||
background-color: $blue;
|
||||
}
|
@ -91,7 +91,23 @@
|
||||
}
|
||||
|
||||
.short-description {
|
||||
margin-top: 8px;
|
||||
margin: 6px 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.api-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -101,12 +117,10 @@
|
||||
|
||||
.github-links {
|
||||
float: right;
|
||||
|
||||
.material-icons {
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
@include font-size(20);
|
||||
|
||||
&:hover {
|
||||
background-color: $mist;
|
||||
}
|
||||
@ -114,6 +128,7 @@
|
||||
}
|
||||
|
||||
.api-body {
|
||||
|
||||
.class-overview {
|
||||
position: relative;
|
||||
|
||||
@ -122,12 +137,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.method-table,
|
||||
.option-table,
|
||||
.list-table {
|
||||
.method-table, .option-table, .list-table {
|
||||
td > code {
|
||||
background-color: inherit;
|
||||
white-space: pre-wrap;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.with-github-links {
|
||||
@ -148,7 +161,7 @@
|
||||
|
||||
h3 {
|
||||
margin: 6px 0;
|
||||
font-weight: 500;
|
||||
font-weight: bold;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
@ -159,9 +172,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.api-heading {
|
||||
padding: 5px 0;
|
||||
@include font-size(14);
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.parameters-table {
|
||||
@ -230,9 +244,7 @@
|
||||
}
|
||||
|
||||
|
||||
.from-constructor,
|
||||
.read-only-property,
|
||||
.write-only-property {
|
||||
.from-constructor, .read-only-property, .write-only-property {
|
||||
@include font-size(12);
|
||||
font-weight: 600;
|
||||
@include letter-spacing(0.5);
|
||||
@ -247,8 +259,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.selector-list,
|
||||
.inherited-members-list {
|
||||
.selector-list, .inherited-members-list {
|
||||
ul {
|
||||
padding: 0;
|
||||
li {
|
||||
@ -259,8 +270,7 @@
|
||||
}
|
||||
|
||||
.selector-list {
|
||||
li,
|
||||
a {
|
||||
li, a {
|
||||
font-weight: bold;
|
||||
i {
|
||||
font-weight: normal;
|
||||
@ -269,39 +279,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.api-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: -4px;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.github-links {
|
||||
float: right;
|
||||
.material-icons {
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
font-size: 20px;
|
||||
&:hover {
|
||||
background-color: $mist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deprecated-api-item {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
code-example,
|
||||
code-tabs {
|
||||
code-example, code-tabs {
|
||||
clear: both;
|
||||
display: block;
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
}
|
||||
|
||||
code-example {
|
||||
|
@ -30,12 +30,12 @@
|
||||
&:hover {
|
||||
color: $accentblue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
&.toc-heading,
|
||||
&.toc-more-items {
|
||||
button.toc-heading,
|
||||
button.toc-more-items {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
background: 0;
|
||||
@ -51,7 +51,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.toc-heading {
|
||||
button.toc-heading {
|
||||
mat-icon.rotating-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
@ -65,7 +65,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.toc-more-items {
|
||||
button.toc-more-items {
|
||||
color: $mediumgray;
|
||||
top: 10px;
|
||||
position: relative;
|
||||
@ -186,7 +186,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative TOC View for Smaller Screens
|
||||
|
@ -84,23 +84,25 @@ class ExampleZipper {
|
||||
const outputFileName = path.join(outputDirName, relativeDirName, exampleZipName + '.zip');
|
||||
let defaultIncludes = ['**/*.ts', '**/*.js', '**/*.es6', '**/*.css', '**/*.html', '**/*.md', '**/*.json', '**/*.png', '**/*.svg'];
|
||||
let alwaysIncludes = [
|
||||
'bs-config.json',
|
||||
'e2e/protractor.conf.js',
|
||||
'angular.json',
|
||||
'.editorconfig',
|
||||
'.gitignore',
|
||||
'angular.json',
|
||||
'browserslist',
|
||||
'bs-config.json',
|
||||
'karma.conf.js',
|
||||
'tslint.json',
|
||||
'karma-test-shim.js',
|
||||
'tsconfig.*',
|
||||
'tslint.*',
|
||||
'e2e/protractor.conf.js',
|
||||
'e2e/tsconfig.json',
|
||||
'tsconfig.json',
|
||||
'src/testing/**/*',
|
||||
'src/.babelrc',
|
||||
'src/browserslist',
|
||||
'src/favicon.ico',
|
||||
'src/karma.conf.js',
|
||||
'src/polyfills.ts',
|
||||
'src/test.ts',
|
||||
'src/typings.d.ts',
|
||||
'src/environments/**/*',
|
||||
'src/testing/**/*',
|
||||
'src/tsconfig.*',
|
||||
'src/tslint.*',
|
||||
// Only ignore root package.json
|
||||
'!package.json'
|
||||
];
|
||||
|
@ -46,8 +46,6 @@ if (argv.ivy) {
|
||||
* Must be used in conjunction with --setup as this is when the packages are copied.
|
||||
* e.g. --setup --local
|
||||
*
|
||||
* --ivy to turn on `ivy` mode
|
||||
*
|
||||
* --shard to shard the specs into groups to allow you to run them in parallel
|
||||
* e.g. --shard=0/2 // the even specs: 0, 2, 4, etc
|
||||
* e.g. --shard=1/2 // the odd specs: 1, 3, 5, etc
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"types": []
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/spec",
|
||||
"types": [
|
||||
@ -8,8 +8,8 @@
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
"test.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
|
@ -14,8 +14,7 @@ const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
||||
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
|
||||
|
||||
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
||||
const ANGULAR_DIST_PACKAGES = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
||||
const ANGULAR_DIST_PACKAGES_BUILD_CMD = path.join(ANGULAR_ROOT_DIR, 'scripts/build-packages-dist.sh');
|
||||
const ANGULAR_DIST_PACKAGES = path.resolve(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
||||
|
||||
/**
|
||||
* A tool that can install Angular dependencies for a project from NPM or from the
|
||||
@ -30,17 +29,16 @@ class NgPackagesInstaller {
|
||||
* Create a new installer for a project in the specified directory.
|
||||
*
|
||||
* @param {string} projectDir - the path to the directory containing the project.
|
||||
* @param {object} options - a hash of options for the install:
|
||||
* @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.
|
||||
* (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.
|
||||
* * `force` (`boolean`) - whether to force a local installation
|
||||
* even if there is a local marker file.
|
||||
* * `ignorePackages` (`string[]`) - a collection of names of packages
|
||||
* that should not be copied over.
|
||||
*/
|
||||
constructor(projectDir, options = {}) {
|
||||
this.debug = this._parseBooleanArg(options.debug);
|
||||
this.force = this._parseBooleanArg(options.force);
|
||||
this.buildPackages = this._parseBooleanArg(options.buildPackages);
|
||||
this.debug = options.debug;
|
||||
this.force = options.force;
|
||||
this.ignorePackages = options.ignorePackages || [];
|
||||
this.projectDir = path.resolve(projectDir);
|
||||
this.localMarkerPath = path.resolve(this.projectDir, LOCAL_MARKER_PATH);
|
||||
@ -162,31 +160,6 @@ class NgPackagesInstaller {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the local Angular packages.
|
||||
*
|
||||
* NOTE:
|
||||
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
|
||||
* do it themselves (e.g. using Windows Subsystem for Linux or a docker container).
|
||||
*/
|
||||
_buildDistPackages() {
|
||||
const canBuild = process.platform !== 'win32';
|
||||
|
||||
if (canBuild) {
|
||||
this._log(`Building the Angular packages with: ${ANGULAR_DIST_PACKAGES_BUILD_CMD}`);
|
||||
shelljs.exec(ANGULAR_DIST_PACKAGES_BUILD_CMD);
|
||||
} else {
|
||||
this._warn([
|
||||
'Automatically building the local Angular packages is currently not supported on Windows.',
|
||||
`Please, ensure '${ANGULAR_DIST_PACKAGES}' exists and is up-to-date (e.g. by running ` +
|
||||
`'${ANGULAR_DIST_PACKAGES_BUILD_CMD}' in Git Bash for Windows, Windows Subsystem for Linux or a Linux ` +
|
||||
'docker container or VM).',
|
||||
'',
|
||||
'Proceeding anyway...',
|
||||
].join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
_collectDependencies(dependencies, packages) {
|
||||
const peerDependencies = Object.create(null);
|
||||
const mergedDependencies = Object.assign(Object.create(null), dependencies);
|
||||
@ -211,18 +184,13 @@ class NgPackagesInstaller {
|
||||
|
||||
/**
|
||||
* A hash of Angular package configs.
|
||||
* (Detected as directories in '/dist/packages-dist/' that contain a top-level 'package.json' file.)
|
||||
* (Detected as directories in '/packages/' that contain a top-level 'package.json' file.)
|
||||
*/
|
||||
_getDistPackages() {
|
||||
const packageConfigs = Object.create(null);
|
||||
const distDir = ANGULAR_DIST_PACKAGES;
|
||||
|
||||
[ANGULAR_DIST_PACKAGES].forEach(distDir => {
|
||||
this._log(`Angular distributable directory: ${distDir}.`);
|
||||
|
||||
if (this.buildPackages) {
|
||||
this._buildDistPackages();
|
||||
}
|
||||
|
||||
shelljs
|
||||
.find(distDir)
|
||||
.map(filePath => filePath.slice(distDir.length + 1))
|
||||
@ -241,6 +209,8 @@ class NgPackagesInstaller {
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
||||
return packageConfigs;
|
||||
}
|
||||
@ -264,21 +234,6 @@ class NgPackagesInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the value for a boolean cli argument/option. When passing an option multiple times, `yargs` parses it as an
|
||||
* array of boolean values. In that case, we only care about the last occurrence.
|
||||
*
|
||||
* This can be useful, for example, when one has a base command with the option turned on and another command
|
||||
* (building on top of the first one) turning the option off:
|
||||
* ```
|
||||
* "base-command": "my-script --foo --bar",
|
||||
* "no-bar-command": "yarn base-command --no-bar",
|
||||
* ```
|
||||
*/
|
||||
_parseBooleanArg(value) {
|
||||
return Array.isArray(value) ? value.pop() : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return a `yarn.lock` file.
|
||||
*/
|
||||
@ -299,28 +254,17 @@ class NgPackagesInstaller {
|
||||
const restoreCmd = `node ${relativeScriptPath} restore ${absoluteProjectDir}`;
|
||||
|
||||
// Log a warning.
|
||||
this._warn([
|
||||
`The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
||||
'',
|
||||
'To restore the npm packages run:',
|
||||
'',
|
||||
` "${restoreCmd}"`,
|
||||
].join('\n'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning message do draw user's attention.
|
||||
* @param {...string[]} messages - The messages to be logged.
|
||||
*/
|
||||
_warn(...messages) {
|
||||
const lines = messages.join(' ').split('\n');
|
||||
console.warn(chalk.yellow([
|
||||
'',
|
||||
'!'.repeat(110),
|
||||
'!!!',
|
||||
'!!! WARNING',
|
||||
'!!!',
|
||||
...lines.map(line => `!!! ${line}`),
|
||||
`!!! The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
||||
'!!!',
|
||||
'!!! To restore the npm packages run:',
|
||||
'!!!',
|
||||
`!!! "${restoreCmd}"`,
|
||||
'!!!',
|
||||
'!'.repeat(110),
|
||||
'',
|
||||
@ -343,27 +287,24 @@ class NgPackagesInstaller {
|
||||
function main() {
|
||||
shelljs.set('-e');
|
||||
|
||||
const createInstaller = argv => {
|
||||
const {projectDir, ...options} = argv;
|
||||
return new NgPackagesInstaller(projectDir, options);
|
||||
};
|
||||
|
||||
yargs
|
||||
.usage('$0 <cmd> [args]')
|
||||
|
||||
.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 })
|
||||
|
||||
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular distributables.', () => {}, argv => {
|
||||
createInstaller(argv).installLocalDependencies();
|
||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
||||
installer.installLocalDependencies();
|
||||
})
|
||||
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
||||
createInstaller(argv).restoreNpmDependencies();
|
||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
||||
installer.restoreNpmDependencies();
|
||||
})
|
||||
.command('check <projectDir> [--debug]', 'Check that dependencies came from npm. Otherwise display a warning message.', () => {}, argv => {
|
||||
createInstaller(argv).checkDependencies();
|
||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
||||
installer.checkDependencies();
|
||||
})
|
||||
.demandCommand(1, 'Please supply a command from the list above.')
|
||||
.strict()
|
||||
|
@ -8,14 +8,13 @@ const shelljs = require('shelljs');
|
||||
const NgPackagesInstaller = require('./index');
|
||||
|
||||
describe('NgPackagesInstaller', () => {
|
||||
const projectDir = 'root/dir';
|
||||
const absoluteProjectDir = path.resolve(projectDir);
|
||||
const nodeModulesDir = path.resolve(absoluteProjectDir, 'node_modules');
|
||||
const packageJsonPath = path.resolve(absoluteProjectDir, 'package.json');
|
||||
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
|
||||
const ngRootDir = path.resolve(__dirname, '../../..');
|
||||
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
|
||||
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
|
||||
const rootDir = 'root/dir';
|
||||
const absoluteRootDir = path.resolve(rootDir);
|
||||
const nodeModulesDir = path.resolve(absoluteRootDir, 'node_modules');
|
||||
const packageJsonPath = path.resolve(absoluteRootDir, 'package.json');
|
||||
const yarnLockPath = path.resolve(absoluteRootDir, 'yarn.lock');
|
||||
const packagesDir = path.resolve(path.resolve(__dirname, '../../../dist/packages-dist'));
|
||||
const toolsDir = path.resolve(path.resolve(__dirname, '../../../dist/tools/@angular'));
|
||||
let installer;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -26,7 +25,7 @@ describe('NgPackagesInstaller', () => {
|
||||
spyOn(shelljs, 'rm');
|
||||
spyOn(console, 'log');
|
||||
spyOn(console, 'warn');
|
||||
installer = new NgPackagesInstaller(projectDir);
|
||||
installer = new NgPackagesInstaller(rootDir);
|
||||
});
|
||||
|
||||
describe('checkDependencies()', () => {
|
||||
@ -37,14 +36,14 @@ describe('NgPackagesInstaller', () => {
|
||||
it('should not print a warning if there is no _local_.json file', () => {
|
||||
fs.existsSync.and.returnValue(false);
|
||||
installer.checkDependencies();
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
|
||||
expect(installer._printWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should print a warning if there is a _local_.json file', () => {
|
||||
fs.existsSync.and.returnValue(true);
|
||||
installer.checkDependencies();
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
|
||||
expect(installer._printWarning).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -243,68 +242,7 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('_buildDistPackages()', () => {
|
||||
// Call `_buildDistPackages()` with a mock `process.platform` value.
|
||||
const buildDistPackagesOnPlatform = platform => {
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
||||
Object.defineProperty(process, 'platform', {...originalDescriptor, value: platform});
|
||||
installer._buildDistPackages();
|
||||
Object.defineProperty(process, 'platform', originalDescriptor);
|
||||
};
|
||||
|
||||
it('should build the local packages, when not on Windows', () => {
|
||||
const buildScript = path.join(ngRootDir, 'scripts/build-packages-dist.sh');
|
||||
|
||||
buildDistPackagesOnPlatform('linux');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
|
||||
|
||||
shelljs.exec.calls.reset();
|
||||
|
||||
buildDistPackagesOnPlatform('darwin');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
|
||||
|
||||
shelljs.exec.calls.reset();
|
||||
|
||||
buildDistPackagesOnPlatform('anythingButWindows :(');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
|
||||
|
||||
// Ensure that the script does actually exist (e.g. it was not renamed/moved).
|
||||
fs.existsSync.and.callThrough();
|
||||
expect(fs.existsSync(buildScript)).toBe(true);
|
||||
});
|
||||
|
||||
it('should print a warning, when on Windows', () => {
|
||||
buildDistPackagesOnPlatform('win32');
|
||||
const warning = console.warn.calls.argsFor(0)[0];
|
||||
|
||||
expect(shelljs.exec).not.toHaveBeenCalled();
|
||||
expect(warning).toContain(
|
||||
'Automatically building the local Angular 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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getDistPackages()', () => {
|
||||
beforeEach(() => spyOn(NgPackagesInstaller.prototype, '_buildDistPackages'));
|
||||
|
||||
it('should not build the local packages by default', () => {
|
||||
installer._getDistPackages();
|
||||
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should build the local packages, if `buildPackages` is true', () => {
|
||||
installer = new NgPackagesInstaller(projectDir, {buildPackages: true});
|
||||
installer._getDistPackages();
|
||||
expect(installer._buildDistPackages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not build the local packages by default', () => {
|
||||
installer._getDistPackages();
|
||||
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should include top level Angular packages', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
const expectedValue = jasmine.objectContaining({
|
||||
@ -331,7 +269,7 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
|
||||
it('should not include packages that have been ignored', () => {
|
||||
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
|
||||
installer = new NgPackagesInstaller(rootDir, { ignorePackages: ['@angular/router'] });
|
||||
const ngPackages = installer._getDistPackages();
|
||||
|
||||
expect(ngPackages['@angular/common']).toBeDefined();
|
||||
@ -345,9 +283,9 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
|
||||
it('should assign the debug property from the options', () => {
|
||||
installer = new NgPackagesInstaller(projectDir, { debug: true });
|
||||
installer = new NgPackagesInstaller(rootDir, { debug: true });
|
||||
expect(installer.debug).toBe(true);
|
||||
installer = new NgPackagesInstaller(projectDir, { });
|
||||
installer = new NgPackagesInstaller(rootDir, { });
|
||||
expect(installer.debug).toBe(undefined);
|
||||
});
|
||||
|
||||
@ -412,7 +350,7 @@ describe('NgPackagesInstaller', () => {
|
||||
expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);
|
||||
|
||||
// When run for a different directory...
|
||||
const dir2 = projectDir;
|
||||
const dir2 = rootDir;
|
||||
const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer/index restore .*?${path.resolve(dir1)}\\b`);
|
||||
installer = new NgPackagesInstaller(dir2);
|
||||
installer._printWarning('');
|
||||
@ -423,14 +361,14 @@ describe('NgPackagesInstaller', () => {
|
||||
describe('_installDeps()', () => {
|
||||
it('should run yarn install with the given options', () => {
|
||||
installer._installDeps('option-1', 'option-2');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteProjectDir });
|
||||
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteRootDir });
|
||||
});
|
||||
});
|
||||
|
||||
describe('local marker helpers', () => {
|
||||
let installer;
|
||||
beforeEach(() => {
|
||||
installer = new NgPackagesInstaller(projectDir);
|
||||
installer = new NgPackagesInstaller(rootDir);
|
||||
});
|
||||
|
||||
describe('_checkLocalMarker', () => {
|
||||
|
@ -208,9 +208,9 @@
|
||||
<code>{$ renderMemberSyntax(property) $}</code>
|
||||
</td>
|
||||
<td>
|
||||
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-Only</span>{% endif %}
|
||||
{%- if property.isSetAccessor and not property.isGetAccessor %}<span class='write-only-property'>Write-Only</span>{% endif %}
|
||||
{% if property.constructorParamDoc %} <span class='from-constructor'>Declared in Constructor</span>{% endif %}
|
||||
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-only.</span>{% endif %}
|
||||
{%- if property.isSetAccessor and not property.isGetAccessor %}<span class='write-only-property'>Write-only.</span>{% endif %}
|
||||
{% if property.constructorParamDoc %} <span class='from-constructor'>Declared in constructor.</span>{% endif %}
|
||||
{% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %}
|
||||
{$ (property.description or property.constructorParamDoc.description) | marked $}
|
||||
{%- if property.see.length %}
|
||||
|
@ -67,8 +67,15 @@ if [[ $? != 0 ]]; then exit 1; fi
|
||||
grep "_MatMenuBase.ngBaseDef = ɵngcc0.ɵɵdefineBase({ inputs: {" node_modules/@angular/material/esm5/menu.es5.js
|
||||
if [[ $? != 0 ]]; then exit 1; fi
|
||||
|
||||
# Did it handle namespace imported decorators in UMD?
|
||||
# Did it handle namespace imported decorators in UMD using `__decorate` syntax?
|
||||
grep "type: core.Injectable" node_modules/@angular/common/bundles/common.umd.js
|
||||
# (and ensure the @angular/common package is indeed using `__decorate` syntax)
|
||||
grep "JsonPipe = __decorate(" node_modules/@angular/common/bundles/common.umd.js.__ivy_ngcc_bak
|
||||
|
||||
# Did it handle namespace imported decorators in UMD using static properties?
|
||||
grep "type: core.Injectable," node_modules/@angular/cdk/bundles/cdk-a11y.umd.js
|
||||
# (and ensure the @angular/cdk/a11y package is indeed using static properties)
|
||||
grep "FocusMonitor.decorators =" node_modules/@angular/cdk/bundles/cdk-a11y.umd.js.__ivy_ngcc_bak
|
||||
|
||||
# Can it be safely run again (as a noop)?
|
||||
# And check that it logged skipping compilation as expected
|
||||
|
@ -4,7 +4,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../dist/typings_test_ts32/",
|
||||
"outDir": "../../dist/typings_test_ts34/",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
|
@ -4,7 +4,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../dist/typings_test_ts32/",
|
||||
"outDir": "../../dist/typings_test_ts35/",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "8.2.1",
|
||||
"version": "9.0.0-next.0",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
|
@ -161,8 +161,8 @@ export class DatePipe implements PipeTransform {
|
||||
* @param format The date/time components to include, using predefined options or a
|
||||
* custom format string.
|
||||
* @param timezone A timezone offset (such as `'+0430'`), or a standard
|
||||
* UTC/GMT or continental US timezone abbreviation.
|
||||
* When not supplied, uses the end-user's local system timezone.
|
||||
* UTC/GMT or continental US timezone abbreviation. Default is
|
||||
* the local system timezone of the end-user's machine.
|
||||
* @param locale A locale code for the locale format rules to use.
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
|
@ -9,10 +9,10 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, Import, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {BundleProgram} from '../packages/bundle_program';
|
||||
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
|
||||
import {findAll, getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils';
|
||||
|
||||
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||
|
||||
@ -73,6 +73,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
*/
|
||||
protected aliasedClassDeclarations = new Map<ts.Declaration, ts.Identifier>();
|
||||
|
||||
/**
|
||||
* Caches the information of the decorators on a class, as the work involved with extracting
|
||||
* decorators is complex and frequently used.
|
||||
*
|
||||
* This map is lazily populated during the first call to `acquireDecoratorInfo` for a given class.
|
||||
*/
|
||||
protected decoratorCache = new Map<ClassDeclaration, DecoratorInfo>();
|
||||
|
||||
constructor(
|
||||
protected logger: Logger, protected isCore: boolean, checker: ts.TypeChecker,
|
||||
dts?: BundleProgram|null) {
|
||||
@ -247,12 +255,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
|
||||
/** Gets all decorators of the given class symbol. */
|
||||
getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
|
||||
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
|
||||
if (decoratorsProperty) {
|
||||
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
} else {
|
||||
return this.getClassDecoratorsFromHelperCall(symbol);
|
||||
}
|
||||
const {classDecorators} = this.acquireDecoratorInfo(symbol);
|
||||
return classDecorators;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -542,6 +546,72 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return symbol.exports && symbol.exports.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main entry-point for obtaining information on the decorators of a given class. This
|
||||
* information is computed either from static properties if present, or using `tslib.__decorate`
|
||||
* helper calls otherwise. The computed result is cached per class.
|
||||
*
|
||||
* @param classSymbol the class for which decorators should be acquired.
|
||||
* @returns all information of the decorators on the class.
|
||||
*/
|
||||
protected acquireDecoratorInfo(classSymbol: ClassSymbol): DecoratorInfo {
|
||||
if (this.decoratorCache.has(classSymbol.valueDeclaration)) {
|
||||
return this.decoratorCache.get(classSymbol.valueDeclaration) !;
|
||||
}
|
||||
|
||||
// First attempt extracting decorators from static properties.
|
||||
let decoratorInfo = this.computeDecoratorInfoFromStaticProperties(classSymbol);
|
||||
if (decoratorInfo === null) {
|
||||
// If none were present, use the `__decorate` helper calls instead.
|
||||
decoratorInfo = this.computeDecoratorInfoFromHelperCalls(classSymbol);
|
||||
}
|
||||
|
||||
this.decoratorCache.set(classSymbol.valueDeclaration, decoratorInfo);
|
||||
return decoratorInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to compute decorator information from static properties "decorators", "propDecorators"
|
||||
* and "ctorParameters" on the class. If neither of these static properties is present the
|
||||
* library is likely not compiled using tsickle for usage with Closure compiler, in which case
|
||||
* `null` is returned.
|
||||
*
|
||||
* @param classSymbol The class symbol to compute the decorators information for.
|
||||
* @returns All information on the decorators as extracted from static properties, or `null` if
|
||||
* none of the static properties exist.
|
||||
*/
|
||||
protected computeDecoratorInfoFromStaticProperties(classSymbol: ClassSymbol): DecoratorInfo|null {
|
||||
let classDecorators: Decorator[]|null = null;
|
||||
let memberDecorators: Map<string, Decorator[]>|null = null;
|
||||
let constructorParamInfo: ParamInfo[]|null = null;
|
||||
|
||||
const decoratorsProperty = this.getStaticProperty(classSymbol, DECORATORS);
|
||||
if (decoratorsProperty !== undefined) {
|
||||
classDecorators = this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
}
|
||||
|
||||
const propDecoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS);
|
||||
if (propDecoratorsProperty !== undefined) {
|
||||
memberDecorators = this.getMemberDecoratorsFromStaticProperty(propDecoratorsProperty);
|
||||
}
|
||||
|
||||
const constructorParamsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS);
|
||||
if (constructorParamsProperty !== undefined) {
|
||||
constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty);
|
||||
}
|
||||
|
||||
// If none of the static properties were present, no decorator info could be computed.
|
||||
if (classDecorators === null && memberDecorators === null && constructorParamInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
classDecorators,
|
||||
memberDecorators: memberDecorators || new Map<string, Decorator[]>(),
|
||||
constructorParamInfo: constructorParamInfo || [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all class decorators for the given class, where the decorators are declared
|
||||
* via a static property. For example:
|
||||
@ -570,32 +640,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all class decorators for the given class, where the decorators are declared
|
||||
* via the `__decorate` helper method. For example:
|
||||
*
|
||||
* ```
|
||||
* let SomeDirective = class SomeDirective {}
|
||||
* SomeDirective = __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* @param symbol the class whose decorators we want to get.
|
||||
* @returns an array of decorators or null if none where found.
|
||||
*/
|
||||
protected getClassDecoratorsFromHelperCall(symbol: ClassSymbol): Decorator[]|null {
|
||||
const decorators: Decorator[] = [];
|
||||
const helperCalls = this.getHelperCallsForClass(symbol, '__decorate');
|
||||
helperCalls.forEach(helperCall => {
|
||||
const {classDecorators} =
|
||||
this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(symbol.name));
|
||||
classDecorators.filter(decorator => this.isFromCore(decorator))
|
||||
.forEach(decorator => decorators.push(decorator));
|
||||
});
|
||||
return decorators.length ? decorators : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a symbol which should be of a class, and return metadata about its members.
|
||||
*
|
||||
@ -606,7 +650,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
const members: ClassMember[] = [];
|
||||
|
||||
// The decorators map contains all the properties that are decorated
|
||||
const decoratorsMap = this.getMemberDecorators(symbol);
|
||||
const {memberDecorators} = this.acquireDecoratorInfo(symbol);
|
||||
|
||||
// Make a copy of the decorators as successfully reflected members delete themselves from the
|
||||
// map, so that any leftovers can be easily dealt with.
|
||||
const decoratorsMap = new Map(memberDecorators);
|
||||
|
||||
// The member map contains all the method (instance and static); and any instance properties
|
||||
// that are initialized in the class.
|
||||
@ -675,21 +723,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the member decorators for the given class.
|
||||
* @param classSymbol the class whose member decorators we are interested in.
|
||||
* @returns a map whose keys are the name of the members and whose values are collections of
|
||||
* decorators for the given member.
|
||||
*/
|
||||
protected getMemberDecorators(classSymbol: ClassSymbol): Map<string, Decorator[]> {
|
||||
const decoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS);
|
||||
if (decoratorsProperty) {
|
||||
return this.getMemberDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
} else {
|
||||
return this.getMemberDecoratorsFromHelperCalls(classSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Member decorators may be declared as static properties of the class:
|
||||
*
|
||||
@ -724,7 +757,21 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
}
|
||||
|
||||
/**
|
||||
* Member decorators may be declared via helper call statements.
|
||||
* For a given class symbol, collects all decorator information from tslib helper methods, as
|
||||
* generated by TypeScript into emitted JavaScript files.
|
||||
*
|
||||
* Class decorators are extracted from calls to `tslib.__decorate` that look as follows:
|
||||
*
|
||||
* ```
|
||||
* let SomeDirective = class SomeDirective {}
|
||||
* SomeDirective = __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* The extraction of member decorators is similar, with the distinction that its 2nd and 3rd
|
||||
* argument correspond with a "prototype" target and the name of the member to which the
|
||||
* decorators apply.
|
||||
*
|
||||
* ```
|
||||
* __decorate([
|
||||
@ -733,103 +780,189 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* ], SomeDirective.prototype, "input1", void 0);
|
||||
* ```
|
||||
*
|
||||
* @param classSymbol the class whose member decorators we are interested in.
|
||||
* @returns a map whose keys are the name of the members and whose values are collections of
|
||||
* decorators for the given member.
|
||||
* @param classSymbol The class symbol for which decorators should be extracted.
|
||||
* @returns All information on the decorators of the class.
|
||||
*/
|
||||
protected getMemberDecoratorsFromHelperCalls(classSymbol: ClassSymbol): Map<string, Decorator[]> {
|
||||
const memberDecoratorMap = new Map<string, Decorator[]>();
|
||||
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
|
||||
helperCalls.forEach(helperCall => {
|
||||
const {memberDecorators} = this.reflectDecoratorsFromHelperCall(
|
||||
helperCall, makeMemberTargetFilter(classSymbol.name));
|
||||
memberDecorators.forEach((decorators, memberName) => {
|
||||
if (memberName) {
|
||||
const memberDecorators =
|
||||
memberDecoratorMap.has(memberName) ? memberDecoratorMap.get(memberName) ! : [];
|
||||
const coreDecorators = decorators.filter(decorator => this.isFromCore(decorator));
|
||||
memberDecoratorMap.set(memberName, memberDecorators.concat(coreDecorators));
|
||||
}
|
||||
});
|
||||
});
|
||||
return memberDecoratorMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract decorator info from `__decorate` helper function calls.
|
||||
* @param helperCall the call to a helper that may contain decorator calls
|
||||
* @param targetFilter a function to filter out targets that we are not interested in.
|
||||
* @returns a mapping from member name to decorators, where the key is either the name of the
|
||||
* member or `undefined` if it refers to decorators on the class as a whole.
|
||||
*/
|
||||
protected reflectDecoratorsFromHelperCall(
|
||||
helperCall: ts.CallExpression, targetFilter: TargetFilter):
|
||||
{classDecorators: Decorator[], memberDecorators: Map<string, Decorator[]>} {
|
||||
const classDecorators: Decorator[] = [];
|
||||
protected computeDecoratorInfoFromHelperCalls(classSymbol: ClassSymbol): DecoratorInfo {
|
||||
let classDecorators: Decorator[]|null = null;
|
||||
const memberDecorators = new Map<string, Decorator[]>();
|
||||
const constructorParamInfo: ParamInfo[] = [];
|
||||
|
||||
// First check that the `target` argument is correct
|
||||
if (targetFilter(helperCall.arguments[1])) {
|
||||
// Grab the `decorators` argument which should be an array of calls
|
||||
const decoratorCalls = helperCall.arguments[0];
|
||||
if (decoratorCalls && ts.isArrayLiteralExpression(decoratorCalls)) {
|
||||
decoratorCalls.elements.forEach(element => {
|
||||
// We only care about those elements that are actual calls
|
||||
if (ts.isCallExpression(element)) {
|
||||
const decorator = this.reflectDecoratorCall(element);
|
||||
if (decorator) {
|
||||
const keyArg = helperCall.arguments[2];
|
||||
const keyName = keyArg && ts.isStringLiteral(keyArg) ? keyArg.text : undefined;
|
||||
if (keyName === undefined) {
|
||||
classDecorators.push(decorator);
|
||||
} else {
|
||||
const getConstructorParamInfo = (index: number) => {
|
||||
let param = constructorParamInfo[index];
|
||||
if (param === undefined) {
|
||||
param = constructorParamInfo[index] = {decorators: null, typeExpression: null};
|
||||
}
|
||||
return param;
|
||||
};
|
||||
|
||||
// All relevant information can be extracted from calls to `__decorate`, obtain these first.
|
||||
// Note that although the helper calls are retrieved using the class symbol, the result may
|
||||
// contain helper calls corresponding with unrelated classes. Therefore, each helper call still
|
||||
// has to be checked to actually correspond with the class symbol.
|
||||
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
|
||||
|
||||
for (const helperCall of helperCalls) {
|
||||
if (isClassDecorateCall(helperCall, classSymbol.name)) {
|
||||
// This `__decorate` call is targeting the class itself.
|
||||
const helperArgs = helperCall.arguments[0];
|
||||
|
||||
for (const element of helperArgs.elements) {
|
||||
const entry = this.reflectDecorateHelperEntry(element);
|
||||
if (entry === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.type === 'decorator') {
|
||||
// The helper arg was reflected to represent an actual decorator
|
||||
if (this.isFromCore(entry.decorator)) {
|
||||
(classDecorators || (classDecorators = [])).push(entry.decorator);
|
||||
}
|
||||
} else if (entry.type === 'param:decorators') {
|
||||
// The helper arg represents a decorator for a parameter. Since it's applied to the
|
||||
// class, it corresponds with a constructor parameter of the class.
|
||||
const param = getConstructorParamInfo(entry.index);
|
||||
(param.decorators || (param.decorators = [])).push(entry.decorator);
|
||||
} else if (entry.type === 'params') {
|
||||
// The helper arg represents the types of the parameters. Since it's applied to the
|
||||
// class, it corresponds with the constructor parameters of the class.
|
||||
entry.types.forEach(
|
||||
(type, index) => getConstructorParamInfo(index).typeExpression = type);
|
||||
}
|
||||
}
|
||||
} else if (isMemberDecorateCall(helperCall, classSymbol.name)) {
|
||||
// The `__decorate` call is targeting a member of the class
|
||||
const helperArgs = helperCall.arguments[0];
|
||||
const memberName = helperCall.arguments[2].text;
|
||||
|
||||
for (const element of helperArgs.elements) {
|
||||
const entry = this.reflectDecorateHelperEntry(element);
|
||||
if (entry === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.type === 'decorator') {
|
||||
// The helper arg was reflected to represent an actual decorator.
|
||||
if (this.isFromCore(entry.decorator)) {
|
||||
const decorators =
|
||||
memberDecorators.has(keyName) ? memberDecorators.get(keyName) ! : [];
|
||||
decorators.push(decorator);
|
||||
memberDecorators.set(keyName, decorators);
|
||||
memberDecorators.has(memberName) ? memberDecorators.get(memberName) ! : [];
|
||||
decorators.push(entry.decorator);
|
||||
memberDecorators.set(memberName, decorators);
|
||||
}
|
||||
} else {
|
||||
// Information on decorated parameters is not interesting for ngcc, so it's ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return {classDecorators, memberDecorators};
|
||||
|
||||
return {classDecorators, memberDecorators, constructorParamInfo};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the decorator information from a call to a decorator as a function.
|
||||
* This happens when the decorators has been used in a `__decorate` helper call.
|
||||
* For example:
|
||||
* Extract the details of an entry within a `__decorate` helper call. For example, given the
|
||||
* following code:
|
||||
*
|
||||
* ```
|
||||
* __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* tslib_1.__param(2, Inject(INJECTED_TOKEN)),
|
||||
* tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* Here the `Directive` decorator is decorating `SomeDirective` and the options for
|
||||
* the decorator are passed as arguments to the `Directive()` call.
|
||||
* it can be seen that there are calls to regular decorators (the `Directive`) and calls into
|
||||
* `tslib` functions which have been inserted by TypeScript. Therefore, this function classifies
|
||||
* a call to correspond with
|
||||
* 1. a real decorator like `Directive` above, or
|
||||
* 2. a decorated parameter, corresponding with `__param` calls from `tslib`, or
|
||||
* 3. the type information of parameters, corresponding with `__metadata` call from `tslib`
|
||||
*
|
||||
* @param call the call to the decorator.
|
||||
* @returns a decorator containing the reflected information, or null if the call
|
||||
* is not a valid decorator call.
|
||||
* @param expression the expression that needs to be reflected into a `DecorateHelperEntry`
|
||||
* @returns an object that indicates which of the three categories the call represents, together
|
||||
* with the reflected information of the call, or null if the call is not a valid decorate call.
|
||||
*/
|
||||
protected reflectDecorateHelperEntry(expression: ts.Expression): DecorateHelperEntry|null {
|
||||
// We only care about those elements that are actual calls
|
||||
if (!ts.isCallExpression(expression)) {
|
||||
return null;
|
||||
}
|
||||
const call = expression;
|
||||
|
||||
const helperName = getCalleeName(call);
|
||||
if (helperName === '__metadata') {
|
||||
// This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object
|
||||
// if the metadata key is "design:paramtypes".
|
||||
const key = call.arguments[0];
|
||||
if (key === undefined || !ts.isStringLiteral(key) || key.text !== 'design:paramtypes') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = call.arguments[1];
|
||||
if (value === undefined || !ts.isArrayLiteralExpression(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'params',
|
||||
types: Array.from(value.elements),
|
||||
};
|
||||
}
|
||||
|
||||
if (helperName === '__param') {
|
||||
// This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object.
|
||||
const indexArg = call.arguments[0];
|
||||
const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN;
|
||||
if (isNaN(index)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decoratorCall = call.arguments[1];
|
||||
if (decoratorCall === undefined || !ts.isCallExpression(decoratorCall)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decorator = this.reflectDecoratorCall(decoratorCall);
|
||||
if (decorator === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'param:decorators',
|
||||
index,
|
||||
decorator,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise attempt to reflect it as a regular decorator.
|
||||
const decorator = this.reflectDecoratorCall(call);
|
||||
if (decorator === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'decorator',
|
||||
decorator,
|
||||
};
|
||||
}
|
||||
|
||||
protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null {
|
||||
const decoratorExpression = call.expression;
|
||||
if (isDecoratorIdentifier(decoratorExpression)) {
|
||||
if (!isDecoratorIdentifier(decoratorExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We found a decorator!
|
||||
const decoratorIdentifier =
|
||||
ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name;
|
||||
|
||||
return {
|
||||
name: decoratorIdentifier.text,
|
||||
identifier: decoratorIdentifier,
|
||||
identifier: decoratorExpression,
|
||||
import: this.getImportOfIdentifier(decoratorIdentifier),
|
||||
node: call,
|
||||
args: Array.from(call.arguments)
|
||||
args: Array.from(call.arguments),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given statement to see if it is a call to the specified helper function or null if
|
||||
@ -1070,14 +1203,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
*/
|
||||
protected getConstructorParamInfo(
|
||||
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): CtorParameter[] {
|
||||
const paramsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS);
|
||||
const paramInfo: ParamInfo[]|null = paramsProperty ?
|
||||
this.getParamInfoFromStaticProperty(paramsProperty) :
|
||||
this.getParamInfoFromHelperCall(classSymbol, parameterNodes);
|
||||
const {constructorParamInfo} = this.acquireDecoratorInfo(classSymbol);
|
||||
|
||||
return parameterNodes.map((node, index) => {
|
||||
const {decorators, typeExpression} = paramInfo && paramInfo[index] ?
|
||||
paramInfo[index] :
|
||||
const {decorators, typeExpression} = constructorParamInfo[index] ?
|
||||
constructorParamInfo[index] :
|
||||
{decorators: null, typeExpression: null};
|
||||
const nameNode = node.name;
|
||||
return {
|
||||
@ -1153,58 +1283,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameter type and decorators for a class where the information is stored via
|
||||
* calls to `__decorate` helpers.
|
||||
*
|
||||
* Reflect over the helpers to find the decorators and types about each of
|
||||
* the class's constructor parameters.
|
||||
*
|
||||
* @param classSymbol the class whose parameter info we want to get.
|
||||
* @param parameterNodes the array of TypeScript parameter nodes for this class's constructor.
|
||||
* @returns an array of objects containing the type and decorators for each parameter.
|
||||
*/
|
||||
protected getParamInfoFromHelperCall(
|
||||
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): ParamInfo[] {
|
||||
const parameters: ParamInfo[] =
|
||||
parameterNodes.map(() => ({typeExpression: null, decorators: null}));
|
||||
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
|
||||
helperCalls.forEach(helperCall => {
|
||||
const {classDecorators} =
|
||||
this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(classSymbol.name));
|
||||
classDecorators.forEach(call => {
|
||||
switch (call.name) {
|
||||
case '__metadata':
|
||||
const metadataArg = call.args && call.args[0];
|
||||
const typesArg = call.args && call.args[1];
|
||||
const isParamTypeDecorator = metadataArg && ts.isStringLiteral(metadataArg) &&
|
||||
metadataArg.text === 'design:paramtypes';
|
||||
const types = typesArg && ts.isArrayLiteralExpression(typesArg) && typesArg.elements;
|
||||
if (isParamTypeDecorator && types) {
|
||||
types.forEach((type, index) => parameters[index].typeExpression = type);
|
||||
}
|
||||
break;
|
||||
case '__param':
|
||||
const paramIndexArg = call.args && call.args[0];
|
||||
const decoratorCallArg = call.args && call.args[1];
|
||||
const paramIndex = paramIndexArg && ts.isNumericLiteral(paramIndexArg) ?
|
||||
parseInt(paramIndexArg.text, 10) :
|
||||
NaN;
|
||||
const decorator = decoratorCallArg && ts.isCallExpression(decoratorCallArg) ?
|
||||
this.reflectDecoratorCall(decoratorCallArg) :
|
||||
null;
|
||||
if (!isNaN(paramIndex) && decorator) {
|
||||
const decorators = parameters[paramIndex].decorators =
|
||||
parameters[paramIndex].decorators || [];
|
||||
decorators.push(decorator);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search statements related to the given class for calls to the specified helper.
|
||||
* @param classSymbol the class whose helper calls we are interested in.
|
||||
@ -1377,6 +1455,72 @@ export type ParamInfo = {
|
||||
typeExpression: ts.Expression | null
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a call to `tslib.__metadata` as present in `tslib.__decorate` calls. This is a
|
||||
* synthetic decorator inserted by TypeScript that contains reflection information about the
|
||||
* target of the decorator, i.e. the class or property.
|
||||
*/
|
||||
export interface ParameterTypes {
|
||||
type: 'params';
|
||||
types: ts.Expression[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to `tslib.__param` as present in `tslib.__decorate` calls. This contains
|
||||
* information on any decorators were applied to a certain parameter.
|
||||
*/
|
||||
export interface ParameterDecorators {
|
||||
type: 'param:decorators';
|
||||
index: number;
|
||||
decorator: Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to a decorator as it was present in the original source code, as present in
|
||||
* `tslib.__decorate` calls.
|
||||
*/
|
||||
export interface DecoratorCall {
|
||||
type: 'decorator';
|
||||
decorator: Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the different kinds of decorate helpers that may be present as first argument to
|
||||
* `tslib.__decorate`, as follows:
|
||||
*
|
||||
* ```
|
||||
* __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* tslib_1.__param(2, Inject(INJECTED_TOKEN)),
|
||||
* tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*/
|
||||
export type DecorateHelperEntry = ParameterTypes | ParameterDecorators | DecoratorCall;
|
||||
|
||||
/**
|
||||
* The recorded decorator information of a single class. This information is cached in the host.
|
||||
*/
|
||||
interface DecoratorInfo {
|
||||
/**
|
||||
* All decorators that were present on the class. If no decorators were present, this is `null`
|
||||
*/
|
||||
classDecorators: Decorator[]|null;
|
||||
|
||||
/**
|
||||
* All decorators per member of the class they were present on.
|
||||
*/
|
||||
memberDecorators: Map<string, Decorator[]>;
|
||||
|
||||
/**
|
||||
* Represents the constructor parameter information, such as the type of a parameter and all
|
||||
* decorators for a certain parameter. Indices in this array correspond with the parameter's index
|
||||
* in the constructor. Note that this array may be sparse, i.e. certain constructor parameters may
|
||||
* not have any info recorded.
|
||||
*/
|
||||
constructorParamInfo: ParamInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A statement node that represents an assignment.
|
||||
*/
|
||||
@ -1397,27 +1541,55 @@ export function isAssignment(node: ts.Node): node is ts.AssignmentExpression<ts.
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of a function that can be used to filter out helpers based on their target.
|
||||
* This is used in `reflectDecoratorsFromHelperCall()`.
|
||||
* Tests whether the provided call expression targets a class, by verifying its arguments are
|
||||
* according to the following form:
|
||||
*
|
||||
* ```
|
||||
* __decorate([], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* @param call the call expression that is tested to represent a class decorator call.
|
||||
* @param className the name of the class that the call needs to correspond with.
|
||||
*/
|
||||
export type TargetFilter = (target: ts.Expression) => boolean;
|
||||
export function isClassDecorateCall(call: ts.CallExpression, className: string):
|
||||
call is ts.CallExpression&{arguments: [ts.ArrayLiteralExpression, ts.Expression]} {
|
||||
const helperArgs = call.arguments[0];
|
||||
if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that tests whether the given expression is a class target.
|
||||
* @param className the name of the class we want to target.
|
||||
*/
|
||||
export function makeClassTargetFilter(className: string): TargetFilter {
|
||||
return (target: ts.Expression): boolean => ts.isIdentifier(target) && target.text === className;
|
||||
const target = call.arguments[1];
|
||||
return target !== undefined && ts.isIdentifier(target) && target.text === className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that tests whether the given expression is a class member target.
|
||||
* @param className the name of the class we want to target.
|
||||
* Tests whether the provided call expression targets a member of the class, by verifying its
|
||||
* arguments are according to the following form:
|
||||
*
|
||||
* ```
|
||||
* __decorate([], SomeDirective.prototype, "member", void 0);
|
||||
* ```
|
||||
*
|
||||
* @param call the call expression that is tested to represent a member decorator call.
|
||||
* @param className the name of the class that the call needs to correspond with.
|
||||
*/
|
||||
export function makeMemberTargetFilter(className: string): TargetFilter {
|
||||
return (target: ts.Expression): boolean => ts.isPropertyAccessExpression(target) &&
|
||||
ts.isIdentifier(target.expression) && target.expression.text === className &&
|
||||
target.name.text === 'prototype';
|
||||
export function isMemberDecorateCall(call: ts.CallExpression, className: string):
|
||||
call is ts.CallExpression&
|
||||
{arguments: [ts.ArrayLiteralExpression, ts.StringLiteral, ts.StringLiteral]} {
|
||||
const helperArgs = call.arguments[0];
|
||||
if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = call.arguments[1];
|
||||
if (target === undefined || !ts.isPropertyAccessExpression(target) ||
|
||||
!ts.isIdentifier(target.expression) || target.expression.text !== className ||
|
||||
target.name.text !== 'prototype') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const memberName = call.arguments[2];
|
||||
return memberName !== undefined && ts.isStringLiteral(memberName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1435,10 +1607,10 @@ export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression
|
||||
*/
|
||||
function getCalleeName(call: ts.CallExpression): string|null {
|
||||
if (ts.isIdentifier(call.expression)) {
|
||||
return call.expression.text;
|
||||
return stripDollarSuffix(call.expression.text);
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(call.expression)) {
|
||||
return call.expression.name.text;
|
||||
return stripDollarSuffix(call.expression.name.text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {getNameText, hasNameIdentifier} from '../utils';
|
||||
import {getNameText, hasNameIdentifier, stripDollarSuffix} from '../utils';
|
||||
|
||||
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||
|
||||
@ -659,7 +659,9 @@ function reflectArrayElement(element: ts.Expression) {
|
||||
* helper.
|
||||
*/
|
||||
function getTsHelperFn(node: ts.NamedDeclaration): TsHelperFn|null {
|
||||
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
|
||||
const name = node.name !== undefined && ts.isIdentifier(node.name) ?
|
||||
stripDollarSuffix(node.name.text) :
|
||||
null;
|
||||
|
||||
if (name === '__spread') {
|
||||
return TsHelperFn.Spread;
|
||||
|
@ -165,14 +165,12 @@ export function renderDefinitions(
|
||||
translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const print = (stmt: Statement) =>
|
||||
printer.printNode(ts.EmitHint.Unspecified, translate(stmt), sourceFile);
|
||||
const definitions = compiledClass.compilation
|
||||
.map(
|
||||
c => [createAssignmentStatement(name, c.name, c.initializer)]
|
||||
.concat(c.statements)
|
||||
.map(print)
|
||||
.join('\n'))
|
||||
.join('\n');
|
||||
return definitions;
|
||||
const statements: Statement[] =
|
||||
compiledClass.compilation.map(c => createAssignmentStatement(name, c.name, c.initializer));
|
||||
for (const c of compiledClass.compilation) {
|
||||
statements.push(...c.statements);
|
||||
}
|
||||
return statements.map(print).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,3 +82,14 @@ export function resolveFileWithPostfixes(
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier may become repeated when bundling multiple source files into a single bundle, so
|
||||
* bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. This function
|
||||
* strips off such suffixes, so that ngcc deals with the canonical name of an identifier.
|
||||
* @param value The value to strip any suffix of, if applicable.
|
||||
* @returns The canonical representation of the value, without any suffix.
|
||||
*/
|
||||
export function stripDollarSuffix(value: string): string {
|
||||
return value.replace(/\$\d+$/, '');
|
||||
}
|
||||
|
@ -78,6 +78,22 @@ export function convertToDirectTsLibImport(filesystem: TestFile[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export function convertToInlineTsLib(filesystem: TestFile[], suffix: string = '') {
|
||||
return filesystem.map(file => {
|
||||
const contents = file.contents
|
||||
.replace(`import * as tslib_1 from 'tslib';`, `
|
||||
var __decorate${suffix} = null;
|
||||
var __metadata${suffix} = null;
|
||||
var __read${suffix} = null;
|
||||
var __values${suffix} = null;
|
||||
var __param${suffix} = null;
|
||||
var __extends${suffix} = null;
|
||||
var __assign${suffix} = null;
|
||||
`).replace(/tslib_1\.([_a-z]+)/gi, '$1' + suffix.replace('$', '$$'));
|
||||
return {...file, contents};
|
||||
});
|
||||
}
|
||||
|
||||
export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] {
|
||||
return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name));
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ exports.OtherDirective = OtherDirective;
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier.getText()).toEqual('core.Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
|
@ -951,23 +951,6 @@ exports.ExternalModule = ExternalModule;
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'};
|
||||
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Directive');
|
||||
});
|
||||
|
||||
describe('(returned decorators `args`)', () => {
|
||||
it('should be an empty array if decorator has no `args` property', () => {
|
||||
loadTestFiles([INVALID_DECORATOR_ARGS_FILE]);
|
||||
@ -1185,22 +1168,17 @@ exports.ExternalModule = ExternalModule;
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import;
|
||||
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
|
||||
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Directive');
|
||||
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
});
|
||||
|
||||
describe('(returned prop decorators `args`)', () => {
|
||||
@ -1430,24 +1408,19 @@ exports.ExternalModule = ExternalModule;
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'};
|
||||
const spy = spyOn(CommonJsReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.returnValue(mockImportInfo);
|
||||
|
||||
const parameters = host.getConstructorParameters(classNode);
|
||||
const decorators = parameters ![2].decorators !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Inject');
|
||||
expect(decorators[0].name).toBe('Inject');
|
||||
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {convertToDirectTsLibImport, makeTestBundleProgram} from '../helpers/utils';
|
||||
import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils';
|
||||
|
||||
import {expectTypeValueReferencesForParameters} from './util';
|
||||
|
||||
@ -111,14 +111,18 @@ runInEachFileSystem(() => {
|
||||
];
|
||||
|
||||
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
||||
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
|
||||
const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2');
|
||||
|
||||
FILES = {
|
||||
'namespaced': NAMESPACED_IMPORT_FILES,
|
||||
'direct import': DIRECT_IMPORT_FILES,
|
||||
'inline': INLINE_FILES,
|
||||
'inline suffixed': INLINE_SUFFIXED_FILES,
|
||||
};
|
||||
});
|
||||
|
||||
['namespaced', 'direct import'].forEach(label => {
|
||||
['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => {
|
||||
describe(`[${label}]`, () => {
|
||||
beforeEach(() => {
|
||||
const fs = getFileSystem();
|
||||
@ -141,35 +145,13 @@ runInEachFileSystem(() => {
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier.getText()).toEqual('Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const spy =
|
||||
spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.callFake(
|
||||
(identifier: ts.Identifier) => identifier.getText() === 'Directive' ?
|
||||
{from: '@angular/core', name: 'Directive'} :
|
||||
{});
|
||||
|
||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||
const host =
|
||||
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
|
||||
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toEqual({from: '@angular/core', name: 'Directive'});
|
||||
|
||||
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
|
||||
expect(identifiers.some(identifier => identifier === 'Directive')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support decorators being used inside @angular/core', () => {
|
||||
const {program} =
|
||||
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
||||
@ -185,6 +167,7 @@ runInEachFileSystem(() => {
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier.getText()).toEqual('Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: './directives'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
@ -272,21 +255,6 @@ runInEachFileSystem(() => {
|
||||
expect(staticProperty.value !.getText()).toEqual(`'static'`);
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const spy =
|
||||
spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({});
|
||||
|
||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||
const host =
|
||||
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
|
||||
|
||||
host.getMembersOfClass(classNode);
|
||||
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
|
||||
expect(identifiers.some(identifier => identifier === 'Input')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support decorators being used inside @angular/core', () => {
|
||||
const {program} =
|
||||
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
||||
|
@ -763,11 +763,7 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const mockImportInfo = { from: '@angular/core' } as Import;
|
||||
const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.returnValue(mockImportInfo);
|
||||
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
@ -776,10 +772,7 @@ runInEachFileSystem(() => {
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Directive');
|
||||
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
});
|
||||
|
||||
describe('(returned decorators `args`)', () => {
|
||||
@ -839,11 +832,13 @@ runInEachFileSystem(() => {
|
||||
expect(input1.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input1.isStatic).toEqual(false);
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
|
||||
|
||||
const input2 = members.find(member => member.name === 'input2') !;
|
||||
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input2.isStatic).toEqual(false);
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
expect(input2.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
|
||||
});
|
||||
|
||||
it('should find non decorated properties on a class', () => {
|
||||
@ -991,35 +986,6 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
let callCount = 0;
|
||||
const spy =
|
||||
spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.callFake(() => {
|
||||
callCount++;
|
||||
return {name: `name${callCount}`, from: '@angular/core'};
|
||||
});
|
||||
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
||||
const members = host.getMembersOfClass(classNode);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy.calls.allArgs().map(arg => arg[0].getText())).toEqual([
|
||||
'Input',
|
||||
'Input',
|
||||
'HostBinding',
|
||||
'Input',
|
||||
'HostListener',
|
||||
]);
|
||||
|
||||
const member = members.find(member => member.name === 'input1') !;
|
||||
expect(member.decorators !.length).toBe(1);
|
||||
expect(member.decorators ![0].import).toEqual({name: 'name1', from: '@angular/core'});
|
||||
});
|
||||
|
||||
describe('(returned prop decorators `args`)', () => {
|
||||
it('should be an empty array if prop decorator has no `args` property', () => {
|
||||
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
|
||||
@ -1311,11 +1277,7 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const mockImportInfo: Import = {name: 'mock', from: '@angular/core'};
|
||||
const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.returnValue(mockImportInfo);
|
||||
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
@ -1325,10 +1287,7 @@ runInEachFileSystem(() => {
|
||||
const decorators = parameters[2].decorators !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Inject');
|
||||
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers';
|
||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {convertToDirectTsLibImport, makeTestBundleProgram} from '../helpers/utils';
|
||||
import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils';
|
||||
|
||||
import {expectTypeValueReferencesForParameters} from './util';
|
||||
|
||||
@ -132,14 +132,18 @@ export { SomeDirective };
|
||||
];
|
||||
|
||||
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
||||
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
|
||||
const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2');
|
||||
|
||||
FILES = {
|
||||
'namespaced': NAMESPACED_IMPORT_FILES,
|
||||
'direct import': DIRECT_IMPORT_FILES,
|
||||
'inline': INLINE_FILES,
|
||||
'inline suffixed': INLINE_SUFFIXED_FILES,
|
||||
};
|
||||
});
|
||||
|
||||
['namespaced', 'direct import'].forEach(label => {
|
||||
['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => {
|
||||
describe(`[${label}]`, () => {
|
||||
beforeEach(() => {
|
||||
const fs = getFileSystem();
|
||||
@ -161,34 +165,13 @@ export { SomeDirective };
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier.getText()).toEqual('Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const spy =
|
||||
spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.callFake(
|
||||
(identifier: ts.Identifier) => identifier.getText() === 'Directive' ?
|
||||
{from: '@angular/core', name: 'Directive'} :
|
||||
{});
|
||||
|
||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
|
||||
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toEqual({from: '@angular/core', name: 'Directive'});
|
||||
|
||||
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
|
||||
expect(identifiers.some(identifier => identifier === 'Directive')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support decorators being used inside @angular/core', () => {
|
||||
const {program} =
|
||||
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
||||
@ -203,6 +186,7 @@ export { SomeDirective };
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier.getText()).toEqual('Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: './directives'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
@ -270,20 +254,6 @@ export { SomeDirective };
|
||||
expect(staticProperty.value !.getText()).toEqual(`'static'`);
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const spy =
|
||||
spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({});
|
||||
|
||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
|
||||
|
||||
host.getMembersOfClass(classNode);
|
||||
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
|
||||
expect(identifiers.some(identifier => identifier === 'Input')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support decorators being used inside @angular/core', () => {
|
||||
const {program} =
|
||||
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
||||
@ -319,12 +289,7 @@ export { SomeDirective };
|
||||
});
|
||||
|
||||
describe('(returned parameters `decorators`)', () => {
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const mockImportInfo = {} as Import;
|
||||
const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.returnValue(mockImportInfo);
|
||||
|
||||
|
||||
it('should have import information on decorators', () => {
|
||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||
const host =
|
||||
new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
@ -334,10 +299,7 @@ export { SomeDirective };
|
||||
const decorators = parameters ![2].decorators !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Inject');
|
||||
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -942,11 +942,7 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import;
|
||||
const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.returnValue(mockImportInfo);
|
||||
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
@ -955,10 +951,7 @@ runInEachFileSystem(() => {
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Directive');
|
||||
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
});
|
||||
|
||||
describe('(returned decorators `args`)', () => {
|
||||
@ -1019,11 +1012,13 @@ runInEachFileSystem(() => {
|
||||
expect(input1.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input1.isStatic).toEqual(false);
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
|
||||
|
||||
const input2 = members.find(member => member.name === 'input2') !;
|
||||
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input2.isStatic).toEqual(false);
|
||||
expect(input2.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
|
||||
});
|
||||
|
||||
it('should find decorated members on a class', () => {
|
||||
@ -1232,30 +1227,6 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
let callCount = 0;
|
||||
const spy =
|
||||
spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.callFake(() => {
|
||||
callCount++;
|
||||
return {name: `name${callCount}`, from: `@angular/core`};
|
||||
});
|
||||
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const members = host.getMembersOfClass(classNode);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
spy.calls.allArgs().forEach(arg => expect(arg[0].getText()).toEqual('Input'));
|
||||
|
||||
const index = members.findIndex(member => member.name === 'input1');
|
||||
expect(members[index].decorators !.length).toBe(1);
|
||||
expect(members[index].decorators ![0].import)
|
||||
.toEqual({name: 'name1', from: '@angular/core'});
|
||||
});
|
||||
|
||||
describe('(returned prop decorators `args`)', () => {
|
||||
it('should be an empty array if prop decorator has no `args` property', () => {
|
||||
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
|
||||
@ -1466,11 +1437,7 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
const mockImportInfo = { name: 'mock', from: '@angulare/core' } as Import;
|
||||
const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
|
||||
.and.returnValue(mockImportInfo);
|
||||
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
@ -1480,10 +1447,7 @@ runInEachFileSystem(() => {
|
||||
const decorators = parameters ![2].decorators !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Inject');
|
||||
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1705,6 +1669,30 @@ runInEachFileSystem(() => {
|
||||
expect(definition.helper).toBe(TsHelperFn.Spread);
|
||||
expect(definition.parameters.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should recognize TypeScript __spread helper function implementation when suffixed',
|
||||
() => {
|
||||
const file: TestFile = {
|
||||
name: _('/implementation.js'),
|
||||
contents: `
|
||||
var __spread$2 = (this && this.__spread$2) || function () {
|
||||
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
|
||||
return ar;
|
||||
};`,
|
||||
};
|
||||
loadTestFiles([file]);
|
||||
const {program} = makeTestBundleProgram(file.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
|
||||
const node =
|
||||
getDeclaration(program, file.name, '__spread$2', ts.isVariableDeclaration) !;
|
||||
|
||||
const definition = host.getDefinitionOfFunction(node) !;
|
||||
expect(definition.node).toBe(node);
|
||||
expect(definition.body).toBeNull();
|
||||
expect(definition.helper).toBe(TsHelperFn.Spread);
|
||||
expect(definition.parameters.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getImportOfIdentifier()', () => {
|
||||
|
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {absoluteFrom} from '../../../src/ngtsc/file_system';
|
||||
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {ClassMemberKind, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadTestFiles} from '../../../test/helpers';
|
||||
import {UmdReflectionHost} from '../../src/host/umd_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {makeTestBundleProgram} from '../helpers/utils';
|
||||
|
||||
import {expectTypeValueReferencesForParameters} from './util';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('UmdReflectionHost [import helper style]', () => {
|
||||
let _: typeof absoluteFrom;
|
||||
|
||||
let SOME_DIRECTIVE_FILE: TestFile;
|
||||
|
||||
beforeEach(() => {
|
||||
_ = absoluteFrom;
|
||||
|
||||
SOME_DIRECTIVE_FILE = {
|
||||
name: _('/some_directive.umd.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
|
||||
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
|
||||
(factory(global.some_directive,global.ng.core));
|
||||
}(this, (function (exports,core) { 'use strict';
|
||||
|
||||
var __decorate = null;
|
||||
var __metadata = null;
|
||||
var __param = null;
|
||||
|
||||
var INJECTED_TOKEN = new InjectionToken('injected');
|
||||
var ViewContainerRef = {};
|
||||
var TemplateRef = {};
|
||||
|
||||
var SomeDirective = (function() {
|
||||
function SomeDirective(_viewContainer, _template, injected) {}
|
||||
__decorate([
|
||||
core.Input(),
|
||||
__metadata("design:type", String)
|
||||
], SomeDirective.prototype, "input1", void 0);
|
||||
__decorate([
|
||||
core.Input(),
|
||||
__metadata("design:type", Number)
|
||||
], SomeDirective.prototype, "input2", void 0);
|
||||
SomeDirective = __decorate([
|
||||
core.Directive({ selector: '[someDirective]' }),
|
||||
__param(2, core.Inject(INJECTED_TOKEN)),
|
||||
__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
|
||||
], SomeDirective);
|
||||
return SomeDirective;
|
||||
}());
|
||||
exports.SomeDirective = SomeDirective;
|
||||
})));`,
|
||||
};
|
||||
});
|
||||
|
||||
describe('getDecoratorsOfDeclaration()', () => {
|
||||
it('should find the decorators on a class', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators).toBeDefined();
|
||||
expect(decorators.length).toEqual(1);
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier.getText()).toEqual('core.Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMembersOfClass()', () => {
|
||||
it('should find decorated members on a class', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const members = host.getMembersOfClass(classNode);
|
||||
|
||||
const input1 = members.find(member => member.name === 'input1') !;
|
||||
expect(input1.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input1.isStatic).toEqual(false);
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
|
||||
const input2 = members.find(member => member.name === 'input2') !;
|
||||
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input2.isStatic).toEqual(false);
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
});
|
||||
|
||||
describe('getConstructorParameters', () => {
|
||||
it('should find the decorated constructor parameters', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const parameters = host.getConstructorParameters(classNode);
|
||||
|
||||
expect(parameters).toBeDefined();
|
||||
expect(parameters !.map(parameter => parameter.name)).toEqual([
|
||||
'_viewContainer', '_template', 'injected'
|
||||
]);
|
||||
expectTypeValueReferencesForParameters(parameters !, [
|
||||
'ViewContainerRef',
|
||||
'TemplateRef',
|
||||
null,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1057,22 +1057,17 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'};
|
||||
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
|
||||
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Directive');
|
||||
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
});
|
||||
|
||||
it('should find decorated members on a class at the top level', () => {
|
||||
@ -1290,22 +1285,17 @@ runInEachFileSystem(() => {
|
||||
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
||||
});
|
||||
|
||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||
it('should have import information on decorators', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import;
|
||||
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
|
||||
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
expect(decorators[0].import).toBe(mockImportInfo);
|
||||
|
||||
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
|
||||
expect(typeIdentifier.text).toBe('Directive');
|
||||
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
});
|
||||
|
||||
describe('(returned prop decorators `args`)', () => {
|
||||
|
@ -98,6 +98,7 @@ runInEachFileSystem(() => {
|
||||
let _: typeof absoluteFrom;
|
||||
let INPUT_PROGRAM: TestFile;
|
||||
let COMPONENT_PROGRAM: TestFile;
|
||||
let NGMODULE_PROGRAM: TestFile;
|
||||
let INPUT_PROGRAM_MAP: SourceMapConverter;
|
||||
let RENDERED_CONTENTS: string;
|
||||
let OUTPUT_PROGRAM_MAP: SourceMapConverter;
|
||||
@ -118,6 +119,12 @@ runInEachFileSystem(() => {
|
||||
`import { Component } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: Component, args: [{ selector: 'a', template: '{{ person!.name }}' }] }\n];\n`
|
||||
};
|
||||
|
||||
NGMODULE_PROGRAM = {
|
||||
name: _('/node_modules/test-package/src/ngmodule.js'),
|
||||
contents:
|
||||
`import { NgModule } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: NgModule, args: [{}] }\n];\n`
|
||||
};
|
||||
|
||||
INPUT_PROGRAM_MAP = fromObject({
|
||||
'version': 3,
|
||||
'file': _('/node_modules/test-package/src/file.js'),
|
||||
@ -254,6 +261,25 @@ runInEachFileSystem(() => {
|
||||
.toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`);
|
||||
});
|
||||
|
||||
it('should render static fields before any additional statements', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
testFormatter} = createTestRenderer('test-package', [NGMODULE_PROGRAM]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
const definitions: string = addDefinitionsSpy.calls.first().args[2];
|
||||
const ngModuleDef = definitions.indexOf('ngModuleDef');
|
||||
expect(ngModuleDef).not.toEqual(-1, 'ngModuleDef should exist');
|
||||
const ngInjectorDef = definitions.indexOf('ngInjectorDef');
|
||||
expect(ngInjectorDef).not.toEqual(-1, 'ngInjectorDef should exist');
|
||||
const setClassMetadata = definitions.indexOf('setClassMetadata');
|
||||
expect(setClassMetadata).not.toEqual(-1, 'setClassMetadata call should exist');
|
||||
expect(setClassMetadata)
|
||||
.toBeGreaterThan(ngModuleDef, 'setClassMetadata should follow ngModuleDef');
|
||||
expect(setClassMetadata)
|
||||
.toBeGreaterThan(ngInjectorDef, 'setClassMetadata should follow ngInjectorDef');
|
||||
});
|
||||
|
||||
it('should render classes without decorators if handler matches', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
testFormatter} =
|
||||
|
25
packages/compiler-cli/src/ngtsc/indexer/README.md
Normal file
25
packages/compiler-cli/src/ngtsc/indexer/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# `indexer`
|
||||
|
||||
The `indexer` module generates semantic analysis about components used in an
|
||||
Angular project. The module is consumed by a semantic analysis API on an Angular
|
||||
program, which can be invoked separately from the regular Angular compilation
|
||||
pipeline.
|
||||
|
||||
The module is _not_ a fully-featured source code indexer. Rather, it is designed
|
||||
to produce semantic information about an Angular project that can then be used
|
||||
by language analysis tools to generate, for example, cross-references in Angular
|
||||
templates.
|
||||
|
||||
The `indexer` module is developed primarily with the
|
||||
[Kythe](https://github.com/kythe/kythe) ecosystem in mind as an indexing
|
||||
service.
|
||||
|
||||
### Scope of Analysis
|
||||
|
||||
The scope of analysis performed by the module includes
|
||||
|
||||
- indexing template syntax identifiers in a component template
|
||||
- generating information about directives used in a template
|
||||
- generating metadata about component and template source files
|
||||
|
||||
The module does not support indexing TypeScript source code.
|
@ -201,7 +201,7 @@ class TemplateVisitor extends TmplAstRecursiveVisitor {
|
||||
|
||||
const identifiers = ExpressionVisitor.getIdentifiers(
|
||||
attribute.value, expressionSrc, expressionAbsolutePosition, this.boundTemplate,
|
||||
this.targetToIdentifier);
|
||||
this.targetToIdentifier.bind(this));
|
||||
identifiers.forEach(id => this.identifiers.add(id));
|
||||
}
|
||||
visitBoundEvent(attribute: TmplAstBoundEvent) { this.visitExpression(attribute.handler); }
|
||||
|
@ -124,6 +124,32 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover variables in bound attributes', () => {
|
||||
const template = '<div #div [value]="div.innerText"></div>';
|
||||
const refs = getTemplateIdentifiers(bind(template));
|
||||
const elementReference: ElementIdentifier = {
|
||||
name: 'div',
|
||||
kind: IdentifierKind.Element,
|
||||
span: new AbsoluteSourceSpan(1, 4),
|
||||
attributes: new Set(),
|
||||
usedDirectives: new Set(),
|
||||
};
|
||||
const reference: ReferenceIdentifier = {
|
||||
name: 'div',
|
||||
kind: IdentifierKind.Reference,
|
||||
span: new AbsoluteSourceSpan(6, 9),
|
||||
target: {node: elementReference, directive: null},
|
||||
};
|
||||
|
||||
const refArr = Array.from(refs);
|
||||
expect(refArr).toContain({
|
||||
name: 'div',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(19, 22),
|
||||
target: reference,
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover properties in template expressions', () => {
|
||||
const template = '<div [bar]="bar ? bar1 : bar2"></div>';
|
||||
const refs = getTemplateIdentifiers(bind(template));
|
||||
|
@ -377,11 +377,28 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
||||
}
|
||||
|
||||
triggerEventHandler(eventName: string, eventObj: any): void {
|
||||
this.listeners.forEach((listener) => {
|
||||
const node = this.nativeNode as any;
|
||||
const invokedListeners: Function[] = [];
|
||||
|
||||
this.listeners.forEach(listener => {
|
||||
if (listener.name === eventName) {
|
||||
listener.callback(eventObj);
|
||||
const callback = listener.callback;
|
||||
callback(eventObj);
|
||||
invokedListeners.push(callback);
|
||||
}
|
||||
});
|
||||
|
||||
// We need to check whether `eventListeners` exists, because it's something
|
||||
// that Zone.js only adds to `EventTarget` in browser environments.
|
||||
if (typeof node.eventListeners === 'function') {
|
||||
// Note that in Ivy we wrap event listeners with a call to `event.preventDefault` in some
|
||||
// cases. We use `Function` as a special token that gives us access to the actual event
|
||||
// listener.
|
||||
node.eventListeners(eventName).forEach((listener: Function) => {
|
||||
const unwrappedListener = listener(Function);
|
||||
return invokedListeners.indexOf(unwrappedListener) === -1 && unwrappedListener(eventObj);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,11 @@ export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
|
||||
* @publicApi
|
||||
*/
|
||||
export function resolveForwardRef<T>(type: T): T {
|
||||
const fn: any = type;
|
||||
if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
|
||||
fn.__forward_ref__ === forwardRef) {
|
||||
return fn();
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
return isForwardRef(type) ? type() : type;
|
||||
}
|
||||
|
||||
/** Checks whether a function is wrapped by a `forwardRef`. */
|
||||
export function isForwardRef(fn: any): fn is() => any {
|
||||
return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
|
||||
fn.__forward_ref__ === forwardRef;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Type} from '../../interface/type';
|
||||
import {isForwardRef, resolveForwardRef} from '../forward_ref';
|
||||
import {ɵɵinject} from '../injector_compatibility';
|
||||
import {getInjectableDef, getInjectorDef, ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs';
|
||||
|
||||
@ -26,6 +27,14 @@ export const angularCoreDiEnv: {[name: string]: Function} = {
|
||||
|
||||
function getFactoryOf<T>(type: Type<any>): ((type?: Type<T>) => T)|null {
|
||||
const typeAny = type as any;
|
||||
|
||||
if (isForwardRef(type)) {
|
||||
return (() => {
|
||||
const factory = getFactoryOf<T>(resolveForwardRef(typeAny));
|
||||
return factory ? factory() : null;
|
||||
}) as any;
|
||||
}
|
||||
|
||||
const def = getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
|
||||
if (!def || def.factory === undefined) {
|
||||
return null;
|
||||
|
@ -11,16 +11,12 @@
|
||||
import {Subject, Subscription} from 'rxjs';
|
||||
|
||||
/**
|
||||
* Use in components with the `@Output` directive to emit custom events
|
||||
* synchronously or asynchronously, and register handlers for those events
|
||||
* by subscribing to an instance.
|
||||
* Use in directives and components to emit custom events synchronously
|
||||
* or asynchronously, and register handlers for those events by subscribing
|
||||
* to an instance.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* Extends
|
||||
* [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject)
|
||||
* for Angular by adding the `emit()` method.
|
||||
*
|
||||
* In the following example, a component defines two output properties
|
||||
* that create event emitters. When the title is clicked, the emitter
|
||||
* emits an open or close event to toggle the current visibility state.
|
||||
@ -58,7 +54,6 @@ import {Subject, Subscription} from 'rxjs';
|
||||
* <zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
|
||||
* ```
|
||||
*
|
||||
* @see [Observables in Angular](guide/observables-in-angular)
|
||||
* @publicApi
|
||||
*/
|
||||
export class EventEmitter<T extends any> extends Subject<T> {
|
||||
|
@ -140,7 +140,7 @@ export interface Directive {
|
||||
* class BankAccount {
|
||||
* bankName: string;
|
||||
* id: string;
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@ -678,8 +678,6 @@ export interface InputDecorator {
|
||||
* })
|
||||
* class App {}
|
||||
* ```
|
||||
*
|
||||
* @see [Input and Output properties](guide/template-syntax#input-and-output-properties)
|
||||
*/
|
||||
(bindingPropertyName?: string): any;
|
||||
new (bindingPropertyName?: string): any;
|
||||
@ -723,8 +721,6 @@ export interface OutputDecorator {
|
||||
*
|
||||
* See `Input` decorator for an example of providing a binding name.
|
||||
*
|
||||
* @see [Input and Output properties](guide/template-syntax#input-and-output-properties)
|
||||
*
|
||||
*/
|
||||
(bindingPropertyName?: string): any;
|
||||
new (bindingPropertyName?: string): any;
|
||||
|
@ -22,6 +22,21 @@ Great reads:
|
||||
|
||||
See benchmark [here](https://jsperf.com/mono-vs-megamorphic-property-access).
|
||||
|
||||
## Packed vs. holey Array
|
||||
|
||||
V8 represents arrays internally in a different way depending on:
|
||||
- type of elements in the array;
|
||||
- presence of holes (indexes that were never assigned).
|
||||
|
||||
Generally speaking packed arrays (a set of continuous, initialized indexes) perform better as compared to arrays with holes. To assure that arrays are packed follow those guidelines:
|
||||
* create array literals with known values whenever possible (ex. `a = [0];` is better than `a = []; a.push[0];`;
|
||||
* don't use `Array` constructor with the size value (ex. `new Array(5)`) - this will create a `HOLEY_ELEMENTS` array (even if this array is filled in later on!);
|
||||
* don't delete elements from an array (ex. `delete a[0]`) - this will create a hole;
|
||||
* don't write past the array length as this will create holes;
|
||||
|
||||
Great reads:
|
||||
- [Elements kinds in V8](https://v8.dev/blog/elements-kinds)
|
||||
|
||||
## Exporting top level variables
|
||||
|
||||
Exporting top level variables should be avoided where possible where performance
|
||||
|
@ -70,6 +70,7 @@ export function assertLView(value: any) {
|
||||
assertEqual(isLView(value), true, 'Expecting LView');
|
||||
}
|
||||
|
||||
export function assertFirstTemplatePass(tView: TView, errMessage: string) {
|
||||
assertEqual(tView.firstTemplatePass, true, errMessage);
|
||||
export function assertFirstTemplatePass(tView: TView, errMessage?: string) {
|
||||
assertEqual(
|
||||
tView.firstTemplatePass, true, errMessage || 'Should only be called in first template pass.');
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ import {assertComponentType} from './assert';
|
||||
import {getComponentDef} from './definition';
|
||||
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
||||
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
||||
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions/shared';
|
||||
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshDescendantViews} from './instructions/shared';
|
||||
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {TElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {PlayerHandler} from './interfaces/player';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {CONTEXT, FLAGS, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
|
||||
@ -144,8 +144,6 @@ export function renderComponent<T>(
|
||||
component = createRootComponent(
|
||||
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
|
||||
|
||||
addToViewTree(rootView, componentView);
|
||||
|
||||
refreshDescendantViews(rootView); // creation mode pass
|
||||
rootView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
resetPreOrderHookFlags(rootView);
|
||||
@ -184,11 +182,12 @@ export function createRootComponentView(
|
||||
|
||||
if (tView.firstTemplatePass) {
|
||||
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
|
||||
tNode.flags = TNodeFlags.isComponent;
|
||||
markAsComponentHost(tView, tNode);
|
||||
initNodeFlags(tNode, rootView.length, 1);
|
||||
queueComponentIndexForCheck(tNode);
|
||||
}
|
||||
|
||||
addToViewTree(rootView, componentView);
|
||||
|
||||
// Store component view at node index, with node as the HOST
|
||||
return rootView[HEADER_OFFSET] = componentView;
|
||||
}
|
||||
|
@ -193,7 +193,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
component = createRootComponent(
|
||||
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
|
||||
|
||||
addToViewTree(rootLView, componentView);
|
||||
refreshDescendantViews(rootLView);
|
||||
safeToRunHooks = true;
|
||||
} finally {
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isForwardRef, resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {Injector} from '../di/injector';
|
||||
import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility';
|
||||
@ -633,6 +634,14 @@ export class NodeInjector implements Injector {
|
||||
*/
|
||||
export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
|
||||
const typeAny = type as any;
|
||||
|
||||
if (isForwardRef(type)) {
|
||||
return (() => {
|
||||
const factory = ɵɵgetFactoryOf<T>(resolveForwardRef(typeAny));
|
||||
return factory ? factory() : null;
|
||||
}) as any;
|
||||
}
|
||||
|
||||
const def = getComponentDef<T>(typeAny) || getDirectiveDef<T>(typeAny) ||
|
||||
getPipeDef<T>(typeAny) || getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
|
||||
if (!def || def.factory === undefined) {
|
||||
|
@ -185,18 +185,23 @@ export function executeHooks(
|
||||
checkNoChangesMode: boolean, initPhaseState: InitPhaseState,
|
||||
currentNodeIndex: number | null | undefined): void {
|
||||
if (checkNoChangesMode) return;
|
||||
|
||||
if (checkHooks !== null || firstPassHooks !== null) {
|
||||
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
|
||||
firstPassHooks :
|
||||
checkHooks;
|
||||
if (hooksToCall) {
|
||||
if (hooksToCall !== null) {
|
||||
callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// The init phase state must be always checked here as it may have been recursively updated
|
||||
if (currentNodeIndex == null &&
|
||||
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState &&
|
||||
let flags = currentView[FLAGS];
|
||||
if (currentNodeIndex == null && (flags & LViewFlags.InitPhaseStateMask) === initPhaseState &&
|
||||
initPhaseState !== InitPhaseState.InitPhaseCompleted) {
|
||||
currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset;
|
||||
currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer;
|
||||
flags &= LViewFlags.IndexWithinInitPhaseReset;
|
||||
flags += LViewFlags.InitPhaseStateIncrementer;
|
||||
currentView[FLAGS] = flags;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,8 @@ export function ɵɵtemplate(
|
||||
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
|
||||
|
||||
const embeddedTView = tContainerNode.tViews = createTView(
|
||||
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
|
||||
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null,
|
||||
tView.schemas);
|
||||
if (tView.queries !== null) {
|
||||
tView.queries.template(tView, tContainerNode);
|
||||
embeddedTView.queries = tView.queries.embeddedTView(tContainerNode);
|
||||
|
@ -235,7 +235,13 @@ function wrapListener(
|
||||
wrapWithPreventDefault: boolean): EventListener {
|
||||
// Note: we are performing most of the work in the listener function itself
|
||||
// to optimize listener registration.
|
||||
return function wrapListenerIn_markDirtyAndPreventDefault(e: Event) {
|
||||
return function wrapListenerIn_markDirtyAndPreventDefault(e: any) {
|
||||
// Ivy uses `Function` as a special token that allows us to unwrap the function
|
||||
// so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
|
||||
if (e === Function) {
|
||||
return listenerFn;
|
||||
}
|
||||
|
||||
// In order to be backwards compatible with View Engine, events on component host nodes
|
||||
// must also mark the component view itself dirty (i.e. the view that it owns).
|
||||
const startView =
|
||||
|
@ -7,14 +7,13 @@
|
||||
*/
|
||||
import {Injector} from '../../di';
|
||||
import {ErrorHandler} from '../../error_handler';
|
||||
import {Type} from '../../interface/type';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
|
||||
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
|
||||
import {Sanitizer} from '../../sanitization/security';
|
||||
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert';
|
||||
import {createNamedArrayType} from '../../util/named_array_type';
|
||||
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
|
||||
import {assertLView, assertPreviousIsParent} from '../assert';
|
||||
import {assertFirstTemplatePass, assertLView} from '../assert';
|
||||
import {attachPatchData, getComponentViewByInstance} from '../context_discovery';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
|
||||
import {throwMultipleComponentError} from '../errors';
|
||||
@ -26,10 +25,10 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
|
||||
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {renderStylingMap} from '../styling_next/bindings';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
|
||||
@ -63,15 +62,11 @@ export function refreshDescendantViews(lView: LView) {
|
||||
const tView = lView[TVIEW];
|
||||
const creationMode = isCreationMode(lView);
|
||||
|
||||
// This needs to be set before children are processed to support recursive components
|
||||
tView.firstTemplatePass = false;
|
||||
|
||||
// Resetting the bindingIndex of the current LView as the next steps may trigger change detection.
|
||||
if (!creationMode) {
|
||||
// Resetting the bindingIndex of the current LView as the next steps may trigger change
|
||||
// detection.
|
||||
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
||||
|
||||
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
|
||||
// This will be done in the update pass.
|
||||
if (!creationMode) {
|
||||
const checkNoChangesMode = getCheckNoChangesMode();
|
||||
|
||||
executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
|
||||
@ -79,7 +74,9 @@ export function refreshDescendantViews(lView: LView) {
|
||||
refreshDynamicEmbeddedViews(lView);
|
||||
|
||||
// Content query results must be refreshed before content hooks are called.
|
||||
if (tView.contentQueries !== null) {
|
||||
refreshContentQueries(tView, lView);
|
||||
}
|
||||
|
||||
resetPreOrderHookFlags(lView);
|
||||
executeHooks(
|
||||
@ -87,14 +84,22 @@ export function refreshDescendantViews(lView: LView) {
|
||||
InitPhaseState.AfterContentInitHooksToBeRun, undefined);
|
||||
|
||||
setHostBindings(tView, lView);
|
||||
}
|
||||
} else {
|
||||
// This needs to be set before children are processed to support recursive components.
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
// ngFor loop, all the views will be created together before update mode runs and turns
|
||||
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
||||
// matching, etc again and again.
|
||||
tView.firstTemplatePass = false;
|
||||
|
||||
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
|
||||
// content queries are resolved during change detection (i.e. update mode), after embedded
|
||||
// views are refreshed (see block above).
|
||||
if (creationMode && tView.staticContentQueries) {
|
||||
if (tView.staticContentQueries) {
|
||||
refreshContentQueries(tView, lView);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We must materialize query results before child components are processed
|
||||
// in case a child component has projected a container. The LContainer needs
|
||||
@ -103,7 +108,10 @@ export function refreshDescendantViews(lView: LView) {
|
||||
executeViewQueryFn(RenderFlags.Update, tView, lView[CONTEXT]);
|
||||
}
|
||||
|
||||
refreshChildComponents(lView, tView.components);
|
||||
const components = tView.components;
|
||||
if (components !== null) {
|
||||
refreshChildComponents(lView, components);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -111,7 +119,7 @@ export function refreshDescendantViews(lView: LView) {
|
||||
export function setHostBindings(tView: TView, viewData: LView): void {
|
||||
const selectedIndex = getSelectedIndex();
|
||||
try {
|
||||
if (tView.expandoInstructions) {
|
||||
if (tView.expandoInstructions !== null) {
|
||||
let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex;
|
||||
setBindingRoot(bindingRootIndex);
|
||||
let currentDirectiveIndex = -1;
|
||||
@ -179,12 +187,10 @@ function refreshContentQueries(tView: TView, lView: LView): void {
|
||||
}
|
||||
|
||||
/** Refreshes child components in the current view. */
|
||||
function refreshChildComponents(hostLView: LView, components: number[] | null): void {
|
||||
if (components != null) {
|
||||
function refreshChildComponents(hostLView: LView, components: number[]): void {
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
componentRefresh(hostLView, components[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -408,18 +414,9 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
|
||||
// Will become true if the `try` block executes with no errors.
|
||||
let safeToRunHooks = false;
|
||||
try {
|
||||
setPreviousOrParentTNode(null !, true);
|
||||
|
||||
oldView = enterView(viewToRender, viewToRender[T_HOST]);
|
||||
resetPreOrderHookFlags(viewToRender);
|
||||
executeTemplate(viewToRender, tView.template !, getRenderFlags(viewToRender), context);
|
||||
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
// ngFor loop, all the views will be created together before update mode runs and turns
|
||||
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
||||
// matching, etc again and again.
|
||||
tView.firstTemplatePass = false;
|
||||
|
||||
refreshDescendantViews(viewToRender);
|
||||
safeToRunHooks = true;
|
||||
} finally {
|
||||
@ -644,11 +641,14 @@ export function createTView(
|
||||
}
|
||||
|
||||
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
|
||||
const blueprint = new (ngDevMode ? LViewBlueprint ! : Array)(initialViewLength)
|
||||
.fill(null, 0, bindingStartIndex)
|
||||
.fill(NO_CHANGE, bindingStartIndex) as LView;
|
||||
const blueprint = ngDevMode ? new LViewBlueprint !() : [];
|
||||
|
||||
for (let i = 0; i < initialViewLength; i++) {
|
||||
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
|
||||
}
|
||||
blueprint[BINDING_INDEX] = bindingStartIndex;
|
||||
return blueprint;
|
||||
|
||||
return blueprint as LView;
|
||||
}
|
||||
|
||||
export function createError(text: string, token: any) {
|
||||
@ -1029,7 +1029,7 @@ export function resolveDirectives(
|
||||
localRefs: string[] | null): void {
|
||||
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
|
||||
// tsickle.
|
||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
|
||||
if (!getBindingsEnabled()) return;
|
||||
|
||||
@ -1061,6 +1061,10 @@ export function resolveDirectives(
|
||||
|
||||
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
|
||||
|
||||
if (def.contentQueries) {
|
||||
tNode.flags |= TNodeFlags.hasContentQuery;
|
||||
}
|
||||
|
||||
// Init hooks are queued now so ngOnInit is called in host components before
|
||||
// any projected components.
|
||||
registerPreOrderHooks(
|
||||
@ -1087,7 +1091,7 @@ function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
|
||||
addComponentLogic(lView, tNode, def as ComponentDef<any>);
|
||||
}
|
||||
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
|
||||
postProcessDirective(lView, directive, def, i);
|
||||
postProcessDirective(lView, tNode, directive, def, i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1162,20 +1166,15 @@ export function generateExpandoInstructionBlock(
|
||||
* Process a directive on the current node after its creation.
|
||||
*/
|
||||
function postProcessDirective<T>(
|
||||
viewData: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
postProcessBaseDirective(viewData, previousOrParentTNode, directive);
|
||||
ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
|
||||
if (previousOrParentTNode && previousOrParentTNode.attrs) {
|
||||
setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
|
||||
}
|
||||
|
||||
if (viewData[TVIEW].firstTemplatePass && def.contentQueries) {
|
||||
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
|
||||
lView: LView, hostTNode: TNode, directive: T, def: DirectiveDef<T>,
|
||||
directiveDefIdx: number): void {
|
||||
postProcessBaseDirective(lView, hostTNode, directive);
|
||||
if (hostTNode.attrs !== null) {
|
||||
setInputsFromAttrs(directiveDefIdx, directive, def, hostTNode);
|
||||
}
|
||||
|
||||
if (isComponentDef(def)) {
|
||||
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
|
||||
const componentView = getComponentViewByIndex(hostTNode.index, lView);
|
||||
componentView[CONTEXT] = directive;
|
||||
}
|
||||
}
|
||||
@ -1183,16 +1182,12 @@ function postProcessDirective<T>(
|
||||
/**
|
||||
* A lighter version of postProcessDirective() that is used for the root component.
|
||||
*/
|
||||
function postProcessBaseDirective<T>(
|
||||
lView: LView, previousOrParentTNode: TNode, directive: T): void {
|
||||
const native = getNativeByTNode(previousOrParentTNode, lView);
|
||||
|
||||
function postProcessBaseDirective<T>(lView: LView, hostTNode: TNode, directive: T): void {
|
||||
ngDevMode && assertEqual(
|
||||
lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex,
|
||||
'directives should be created before any bindings');
|
||||
ngDevMode && assertPreviousIsParent(getIsParent());
|
||||
|
||||
attachPatchData(directive, lView);
|
||||
const native = getNativeByTNode(hostTNode, lView);
|
||||
if (native) {
|
||||
attachPatchData(native, lView);
|
||||
}
|
||||
@ -1206,7 +1201,9 @@ function postProcessBaseDirective<T>(
|
||||
function findDirectiveMatches(
|
||||
tView: TView, viewData: LView,
|
||||
tNode: TElementNode | TContainerNode | TElementContainerNode): DirectiveDef<any>[]|null {
|
||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
ngDevMode && assertNodeOfPossibleTypes(
|
||||
tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container);
|
||||
const registry = tView.directiveRegistry;
|
||||
let matches: any[]|null = null;
|
||||
if (registry) {
|
||||
@ -1218,8 +1215,7 @@ function findDirectiveMatches(
|
||||
|
||||
if (isComponentDef(def)) {
|
||||
if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
|
||||
tNode.flags = TNodeFlags.isComponent;
|
||||
|
||||
markAsComponentHost(tView, tNode);
|
||||
// The component is always stored first with directives after.
|
||||
matches.unshift(def);
|
||||
} else {
|
||||
@ -1231,13 +1227,16 @@ function findDirectiveMatches(
|
||||
return matches;
|
||||
}
|
||||
|
||||
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
||||
export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode &&
|
||||
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
|
||||
/**
|
||||
* Marks a given TNode as a component's host. This consists of:
|
||||
* - setting appropriate TNode flags;
|
||||
* - storing index of component's host element so it will be queued for view refresh during CD.
|
||||
*/
|
||||
export function markAsComponentHost(tView: TView, hostTNode: TNode): void {
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
hostTNode.flags = TNodeFlags.isComponent;
|
||||
(tView.components || (tView.components = ngDevMode ? new TViewComponents !() : [
|
||||
])).push(previousOrParentTNode.index);
|
||||
])).push(hostTNode.index);
|
||||
}
|
||||
|
||||
|
||||
@ -1305,10 +1304,8 @@ function baseResolveDirective<T>(
|
||||
viewData.push(nodeInjectorFactory);
|
||||
}
|
||||
|
||||
function addComponentLogic<T>(
|
||||
lView: LView, previousOrParentTNode: TNode, def: ComponentDef<T>): void {
|
||||
const native = getNativeByTNode(previousOrParentTNode, lView);
|
||||
|
||||
function addComponentLogic<T>(lView: LView, hostTNode: TNode, def: ComponentDef<T>): void {
|
||||
const native = getNativeByTNode(hostTNode, lView);
|
||||
const tView = getOrCreateTView(def);
|
||||
|
||||
// Only component views should be added to the view tree directly. Embedded views are
|
||||
@ -1317,18 +1314,14 @@ function addComponentLogic<T>(
|
||||
const componentView = addToViewTree(
|
||||
lView, createLView(
|
||||
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
||||
lView[previousOrParentTNode.index], previousOrParentTNode as TElementNode,
|
||||
rendererFactory, rendererFactory.createRenderer(native as RElement, def)));
|
||||
lView[hostTNode.index], hostTNode as TElementNode, rendererFactory,
|
||||
rendererFactory.createRenderer(native as RElement, def)));
|
||||
|
||||
componentView[T_HOST] = previousOrParentTNode as TElementNode;
|
||||
componentView[T_HOST] = hostTNode as TElementNode;
|
||||
|
||||
// Component view will always be created before any injected LContainers,
|
||||
// so this is a regular element, wrap it with the component view
|
||||
lView[previousOrParentTNode.index] = componentView;
|
||||
|
||||
if (lView[TVIEW].firstTemplatePass) {
|
||||
queueComponentIndexForCheck(previousOrParentTNode);
|
||||
}
|
||||
lView[hostTNode.index] = componentView;
|
||||
}
|
||||
|
||||
export function elementAttributeInternal(
|
||||
@ -1494,19 +1487,20 @@ export function createLContainer(
|
||||
* by executing an associated template function.
|
||||
*/
|
||||
function refreshDynamicEmbeddedViews(lView: LView) {
|
||||
for (let current = lView[CHILD_HEAD]; current !== null; current = current[NEXT]) {
|
||||
// Note: current can be an LView or an LContainer instance, but here we are only interested
|
||||
// in LContainer. We can tell it's an LContainer because its length is less than the LView
|
||||
// header.
|
||||
if (current[ACTIVE_INDEX] === -1 && isLContainer(current)) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < current.length; i++) {
|
||||
const dynamicViewData = current[i];
|
||||
let viewOrContainer = lView[CHILD_HEAD];
|
||||
while (viewOrContainer !== null) {
|
||||
// Note: viewOrContainer can be an LView or an LContainer instance, but here we are only
|
||||
// interested in LContainer
|
||||
if (isLContainer(viewOrContainer) && viewOrContainer[ACTIVE_INDEX] === -1) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < viewOrContainer.length; i++) {
|
||||
const embeddedLView = viewOrContainer[i];
|
||||
// The directives and pipes are not needed here as an existing view is only being
|
||||
// refreshed.
|
||||
ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated');
|
||||
renderEmbeddedTemplate(dynamicViewData, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !);
|
||||
ngDevMode && assertDefined(embeddedLView[TVIEW], 'TView must be allocated');
|
||||
renderEmbeddedTemplate(embeddedLView, embeddedLView[TVIEW], embeddedLView[CONTEXT] !);
|
||||
}
|
||||
}
|
||||
viewOrContainer = viewOrContainer[NEXT];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1563,7 +1557,7 @@ export function componentRefresh(hostLView: LView, adjustedElementIndex: number)
|
||||
function syncViewWithBlueprint(componentView: LView) {
|
||||
const componentTView = componentView[TVIEW];
|
||||
for (let i = componentView.length; i < componentTView.blueprint.length; i++) {
|
||||
componentView[i] = componentTView.blueprint[i];
|
||||
componentView.push(componentTView.blueprint[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,8 @@ function findAttrIndexInNode(
|
||||
} else if (
|
||||
maybeAttrName === AttributeMarker.Bindings || maybeAttrName === AttributeMarker.I18n) {
|
||||
bindingsMode = true;
|
||||
} else if (maybeAttrName === AttributeMarker.Classes) {
|
||||
} else if (
|
||||
maybeAttrName === AttributeMarker.Classes || maybeAttrName === AttributeMarker.Styles) {
|
||||
let value = attrs[++i];
|
||||
// We should skip classes here because we have a separate mechanism for
|
||||
// matching classes in projection mode.
|
||||
|
@ -167,8 +167,8 @@ let activeDirectiveSuperClassHeight = 0;
|
||||
*/
|
||||
export function setActiveHostElement(elementIndex: number | null = null) {
|
||||
if (_selectedIndex !== elementIndex) {
|
||||
setSelectedIndex(elementIndex == null ? -1 : elementIndex);
|
||||
activeDirectiveId = elementIndex == null ? 0 : MIN_DIRECTIVE_ID;
|
||||
setSelectedIndex(elementIndex === null ? -1 : elementIndex);
|
||||
activeDirectiveId = elementIndex === null ? 0 : MIN_DIRECTIVE_ID;
|
||||
activeDirectiveSuperClassDepthPosition = 0;
|
||||
activeDirectiveSuperClassHeight = 0;
|
||||
}
|
||||
|
@ -226,6 +226,39 @@ describe('directives', () => {
|
||||
expect(calls).toEqual(['MyDir.ngOnInit']);
|
||||
});
|
||||
|
||||
it('should match directives when the node has "class", "style" and a binding', () => {
|
||||
const logs: string[] = [];
|
||||
|
||||
@Directive({selector: '[test]'})
|
||||
class MyDir {
|
||||
constructor() { logs.push('MyDir.contructor'); }
|
||||
|
||||
@Input('test')
|
||||
myInput = '';
|
||||
|
||||
@Input('disabled')
|
||||
myInput2 = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
// Note that below we're checking the case where the `test` attribute is after
|
||||
// one `class`, one `attribute` and one other binding.
|
||||
template: `
|
||||
<div class="a" style="font-size: 10px;" [disabled]="true" [test]="test"></div>
|
||||
`
|
||||
})
|
||||
class MyComp {
|
||||
test = '';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
|
||||
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(logs).toEqual(['MyDir.contructor']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('outputs', () => {
|
||||
|
@ -6,7 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NO_ERRORS_SCHEMA, NgModule} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
describe('NgModule', () => {
|
||||
@ -74,4 +75,63 @@ describe('NgModule', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('schemas', () => {
|
||||
it('should throw on unknown props if NO_ERRORS_SCHEMA is absent', () => {
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: `
|
||||
<ng-container *ngIf="condition">
|
||||
<div [unknown-prop]="true"></div>
|
||||
</ng-container>
|
||||
`,
|
||||
})
|
||||
class MyComp {
|
||||
condition = true;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [MyComp],
|
||||
})
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [MyModule]});
|
||||
|
||||
expect(() => {
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
}).toThrowError(/Can't bind to 'unknown-prop' since it isn't a known property of 'div'/);
|
||||
});
|
||||
|
||||
it('should not throw on unknown props if NO_ERRORS_SCHEMA is present', () => {
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: `
|
||||
<ng-container *ngIf="condition">
|
||||
<div [unknown-prop]="true"></div>
|
||||
</ng-container>
|
||||
`,
|
||||
})
|
||||
class MyComp {
|
||||
condition = true;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
declarations: [MyComp],
|
||||
})
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [MyModule]});
|
||||
|
||||
expect(() => {
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {Component, Directive, Inject, Injectable, InjectionToken, Injector, NgModule, Optional, forwardRef} from '@angular/core';
|
||||
import {TestBed, async, inject} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('providers', () => {
|
||||
@ -319,6 +320,54 @@ describe('providers', () => {
|
||||
expect(fixture.componentInstance.myService.dep.value).toBe('one');
|
||||
});
|
||||
|
||||
it('should support forward refs in useClass when impl version is also provided', () => {
|
||||
|
||||
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
|
||||
abstract class SomeProvider {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class SomeProviderImpl extends SomeProvider {
|
||||
}
|
||||
|
||||
@Component({selector: 'my-app', template: ''})
|
||||
class App {
|
||||
constructor(public foo: SomeProvider) {}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [App], providers: [{provide: SomeProvider, useClass: SomeProviderImpl}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl);
|
||||
});
|
||||
|
||||
|
||||
onlyInIvy('VE bug (see FW-1454)')
|
||||
.it('should support forward refs in useClass when token is provided', () => {
|
||||
|
||||
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
|
||||
abstract class SomeProvider {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class SomeProviderImpl extends SomeProvider {
|
||||
}
|
||||
|
||||
@Component({selector: 'my-app', template: ''})
|
||||
class App {
|
||||
constructor(public foo: SomeProvider) {}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [App], providers: [{provide: SomeProvider, useClass: SomeProvider}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('flags', () => {
|
||||
|
@ -515,6 +515,9 @@
|
||||
{
|
||||
"name": "locateHostElement"
|
||||
},
|
||||
{
|
||||
"name": "markAsComponentHost"
|
||||
},
|
||||
{
|
||||
"name": "matchTemplateAttribute"
|
||||
},
|
||||
@ -545,9 +548,6 @@
|
||||
{
|
||||
"name": "postProcessDirective"
|
||||
},
|
||||
{
|
||||
"name": "queueComponentIndexForCheck"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
|
@ -386,6 +386,9 @@
|
||||
{
|
||||
"name": "locateHostElement"
|
||||
},
|
||||
{
|
||||
"name": "markAsComponentHost"
|
||||
},
|
||||
{
|
||||
"name": "namespaceHTMLInternal"
|
||||
},
|
||||
@ -410,9 +413,6 @@
|
||||
{
|
||||
"name": "postProcessBaseDirective"
|
||||
},
|
||||
{
|
||||
"name": "queueComponentIndexForCheck"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
|
@ -155,6 +155,9 @@
|
||||
{
|
||||
"name": "isFactoryProvider"
|
||||
},
|
||||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
{
|
||||
"name": "isTypeProvider"
|
||||
},
|
||||
|
@ -1043,6 +1043,9 @@
|
||||
{
|
||||
"name": "isFactory"
|
||||
},
|
||||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
{
|
||||
"name": "isJsObject"
|
||||
},
|
||||
@ -1115,6 +1118,9 @@
|
||||
{
|
||||
"name": "makeParamDecorator"
|
||||
},
|
||||
{
|
||||
"name": "markAsComponentHost"
|
||||
},
|
||||
{
|
||||
"name": "markContextToPersistState"
|
||||
},
|
||||
@ -1175,9 +1181,6 @@
|
||||
{
|
||||
"name": "postProcessDirective"
|
||||
},
|
||||
{
|
||||
"name": "queueComponentIndexForCheck"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
|
@ -8,11 +8,12 @@
|
||||
|
||||
|
||||
import {CommonModule, NgIfContext} from '@angular/common';
|
||||
import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ivyEnabled} from '@angular/private/testing';
|
||||
|
||||
@Injectable()
|
||||
class Logger {
|
||||
@ -653,7 +654,6 @@ class TestCmptWithPropBindings {
|
||||
|
||||
fixture.debugElement.children[1].triggerEventHandler('myevent', <Event>{});
|
||||
expect(fixture.componentInstance.customed).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it('should include classes in properties.className', () => {
|
||||
@ -683,6 +683,61 @@ class TestCmptWithPropBindings {
|
||||
expect(button.properties).toEqual({disabled: true, tabIndex: 1337, title: 'hello'});
|
||||
});
|
||||
|
||||
it('should trigger events registered via Renderer2', () => {
|
||||
@Component({template: ''})
|
||||
class TestComponent implements OnInit {
|
||||
count = 0;
|
||||
eventObj: any;
|
||||
constructor(private renderer: Renderer2, private elementRef: ElementRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.renderer.listen(this.elementRef.nativeElement, 'click', (event: any) => {
|
||||
this.count++;
|
||||
this.eventObj = event;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [TestComponent]});
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
// Ivy depends on `eventListeners` to pick up events that haven't been registered through
|
||||
// Angular templates. At the time of writing Zone.js doesn't add `eventListeners` in Node
|
||||
// environments so we have to skip the test.
|
||||
if (!ivyEnabled || typeof fixture.debugElement.nativeElement.eventListeners === 'function') {
|
||||
const event = {value: true};
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.triggerEventHandler('click', event);
|
||||
expect(fixture.componentInstance.count).toBe(1);
|
||||
expect(fixture.componentInstance.eventObj).toBe(event);
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to trigger an event with a null value', () => {
|
||||
let value = undefined;
|
||||
|
||||
@Component({template: '<button (click)="handleClick($event)"></button>'})
|
||||
class TestComponent {
|
||||
handleClick(_event: any) {
|
||||
value = _event;
|
||||
|
||||
// Returning `false` is what causes the renderer to call `event.preventDefault`.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [TestComponent]});
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
const button = fixture.debugElement.query(By.css('button'));
|
||||
|
||||
expect(() => {
|
||||
button.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}).not.toThrow();
|
||||
|
||||
expect(value).toBeNull();
|
||||
});
|
||||
|
||||
describe('componentInstance on DebugNode', () => {
|
||||
|
||||
it('should return component associated with a node if a node is a component host', () => {
|
||||
|
@ -14,7 +14,7 @@ load(
|
||||
"ROLLUP_ATTRS",
|
||||
"ROLLUP_DEPS_ASPECTS",
|
||||
"run_rollup",
|
||||
"run_terser",
|
||||
# "run_terser",
|
||||
"write_rollup_config",
|
||||
)
|
||||
load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5")
|
||||
@ -25,7 +25,8 @@ load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "f
|
||||
# have the path hardcoded in them.
|
||||
_ROLLUP_OUTPUTS = {
|
||||
"build_umd": "%{name}.umd.js",
|
||||
"build_umd_min": "%{name}.umd.min.js",
|
||||
# min bundle is not used at the moment. Disable to speed up build
|
||||
# "build_umd_min": "%{name}.umd.min.js",
|
||||
}
|
||||
|
||||
DEPS_ASPECTS = [esm5_outputs_aspect]
|
||||
@ -41,8 +42,15 @@ def _ls_rollup_bundle(ctx):
|
||||
output_format = "amd",
|
||||
)
|
||||
run_rollup(ctx, esm5_sources, rollup_config, ctx.outputs.build_umd)
|
||||
source_map = run_terser(ctx, ctx.outputs.build_umd, ctx.outputs.build_umd_min)
|
||||
return DefaultInfo(files = depset([ctx.outputs.build_umd, ctx.outputs.build_umd_min, source_map]))
|
||||
|
||||
# source_map = run_terser(ctx, ctx.outputs.build_umd, ctx.outputs.build_umd_min)
|
||||
return DefaultInfo(
|
||||
files = depset([
|
||||
ctx.outputs.build_umd,
|
||||
# ctx.outputs.build_umd_min,
|
||||
# source_map,
|
||||
]),
|
||||
)
|
||||
|
||||
ls_rollup_bundle = rule(
|
||||
implementation = _ls_rollup_bundle,
|
||||
|
@ -279,7 +279,7 @@ function voidElementAttributeCompletions(info: TemplateInfo, path: AstPath<HtmlA
|
||||
|
||||
class ExpressionVisitor extends NullTemplateVisitor {
|
||||
private getExpressionScope: () => SymbolTable;
|
||||
result: Completions;
|
||||
result: Completion[]|undefined;
|
||||
|
||||
constructor(
|
||||
private info: TemplateInfo, private position: number, private attr?: Attribute,
|
||||
|
@ -6,11 +6,28 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as tss from 'typescript/lib/tsserverlibrary';
|
||||
|
||||
import {TemplateInfo} from './common';
|
||||
import {locateSymbol} from './locate_symbol';
|
||||
import {Definition} from './types';
|
||||
import {Location} from './types';
|
||||
|
||||
export function getDefinition(info: TemplateInfo): Definition {
|
||||
export function getDefinition(info: TemplateInfo): Location[]|undefined {
|
||||
const result = locateSymbol(info);
|
||||
return result && result.symbol.definition;
|
||||
}
|
||||
|
||||
export function ngLocationToTsDefinitionInfo(loc: Location): tss.DefinitionInfo {
|
||||
return {
|
||||
fileName: loc.fileName,
|
||||
textSpan: {
|
||||
start: loc.span.start,
|
||||
length: loc.span.end - loc.span.start,
|
||||
},
|
||||
// TODO(kyliau): Provide more useful info for name, kind and containerKind
|
||||
name: '', // should be name of symbol but we don't have enough information here.
|
||||
kind: tss.ScriptElementKind.unknown,
|
||||
containerName: loc.fileName,
|
||||
containerKind: tss.ScriptElementKind.unknown,
|
||||
};
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {getTemplateCompletions} from './completions';
|
||||
import {getDefinition} from './definitions';
|
||||
import {getDeclarationDiagnostics} from './diagnostics';
|
||||
import {getHover} from './hover';
|
||||
import {Completions, Definition, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
|
||||
import {Completion, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Location, Span, TemplateSource} from './types';
|
||||
import {offsetSpan, spanOf} from './utils';
|
||||
|
||||
|
||||
@ -34,14 +34,14 @@ class LanguageServiceImpl implements LanguageService {
|
||||
|
||||
getTemplateReferences(): string[] { return this.host.getTemplateReferences(); }
|
||||
|
||||
getDiagnostics(fileName: string): Diagnostics|undefined {
|
||||
let results: Diagnostics = [];
|
||||
let templates = this.host.getTemplates(fileName);
|
||||
getDiagnostics(fileName: string): Diagnostic[] {
|
||||
const results: Diagnostic[] = [];
|
||||
const templates = this.host.getTemplates(fileName);
|
||||
if (templates && templates.length) {
|
||||
results.push(...this.getTemplateDiagnostics(fileName, templates));
|
||||
}
|
||||
|
||||
let declarations = this.host.getDeclarations(fileName);
|
||||
const declarations = this.host.getDeclarations(fileName);
|
||||
if (declarations && declarations.length) {
|
||||
const summary = this.host.getAnalyzedModules();
|
||||
results.push(...getDeclarationDiagnostics(declarations, summary));
|
||||
@ -58,14 +58,14 @@ class LanguageServiceImpl implements LanguageService {
|
||||
return [];
|
||||
}
|
||||
|
||||
getCompletionsAt(fileName: string, position: number): Completions {
|
||||
getCompletionsAt(fileName: string, position: number): Completion[]|undefined {
|
||||
let templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||
if (templateInfo) {
|
||||
return getTemplateCompletions(templateInfo);
|
||||
}
|
||||
}
|
||||
|
||||
getDefinitionAt(fileName: string, position: number): Definition {
|
||||
getDefinitionAt(fileName: string, position: number): Location[]|undefined {
|
||||
let templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||
if (templateInfo) {
|
||||
return getDefinition(templateInfo);
|
||||
@ -112,15 +112,11 @@ class LanguageServiceImpl implements LanguageService {
|
||||
}
|
||||
}
|
||||
|
||||
function uniqueBySpan < T extends {
|
||||
span: Span;
|
||||
}
|
||||
> (elements: T[] | undefined): T[]|undefined {
|
||||
if (elements) {
|
||||
function uniqueBySpan<T extends{span: Span}>(elements: T[]): T[] {
|
||||
const result: T[] = [];
|
||||
const map = new Map<number, Set<number>>();
|
||||
for (const element of elements) {
|
||||
let span = element.span;
|
||||
const {span} = element;
|
||||
let set = map.get(span.start);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
@ -132,5 +128,4 @@ function uniqueBySpan < T extends {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
import * as ts from 'typescript'; // used as value, passed in by tsserver at runtime
|
||||
import * as tss from 'typescript/lib/tsserverlibrary'; // used as type only
|
||||
|
||||
import {ngLocationToTsDefinitionInfo} from './definitions';
|
||||
import {createLanguageService} from './language_service';
|
||||
import {Completion, Diagnostic, DiagnosticMessageChain, Location} from './types';
|
||||
import {TypeScriptServiceHost} from './typescript_host';
|
||||
@ -23,7 +24,7 @@ export function getExternalFiles(project: tss.server.Project): string[]|undefine
|
||||
}
|
||||
}
|
||||
|
||||
function completionToEntry(c: Completion): ts.CompletionEntry {
|
||||
function completionToEntry(c: Completion): tss.CompletionEntry {
|
||||
return {
|
||||
// TODO: remove any and fix type error.
|
||||
kind: c.kind as any,
|
||||
@ -44,15 +45,15 @@ function diagnosticChainToDiagnosticChain(chain: DiagnosticMessageChain):
|
||||
}
|
||||
|
||||
function diagnosticMessageToDiagnosticMessageText(message: string | DiagnosticMessageChain): string|
|
||||
ts.DiagnosticMessageChain {
|
||||
tss.DiagnosticMessageChain {
|
||||
if (typeof message === 'string') {
|
||||
return message;
|
||||
}
|
||||
return diagnosticChainToDiagnosticChain(message);
|
||||
}
|
||||
|
||||
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
|
||||
const result = {
|
||||
function diagnosticToDiagnostic(d: Diagnostic, file: tss.SourceFile | undefined): tss.Diagnostic {
|
||||
return {
|
||||
file,
|
||||
start: d.span.start,
|
||||
length: d.span.end - d.span.start,
|
||||
@ -61,154 +62,131 @@ function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnost
|
||||
code: 0,
|
||||
source: 'ng'
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export function create(info: tss.server.PluginCreateInfo): ts.LanguageService {
|
||||
const oldLS: ts.LanguageService = info.languageService;
|
||||
const proxy: ts.LanguageService = Object.assign({}, oldLS);
|
||||
const logger = info.project.projectService.logger;
|
||||
|
||||
function tryOperation<T>(attempting: string, callback: () => T): T|null {
|
||||
try {
|
||||
return callback();
|
||||
} catch (e) {
|
||||
logger.info(`Failed to ${attempting}: ${e.toString()}`);
|
||||
logger.info(`Stack trace: ${e.stack}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const serviceHost = new TypeScriptServiceHost(info.languageServiceHost, oldLS);
|
||||
const ls = createLanguageService(serviceHost);
|
||||
projectHostMap.set(info.project, serviceHost);
|
||||
export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
|
||||
const {project, languageService: tsLS, languageServiceHost: tsLSHost, config} = info;
|
||||
// This plugin could operate under two different modes:
|
||||
// 1. TS + Angular
|
||||
// Plugin augments TS language service to provide additional Angular
|
||||
// information. This only works with inline templates and is meant to be
|
||||
// used as a local plugin (configured via tsconfig.json)
|
||||
// 2. Angular only
|
||||
// Plugin only provides information on Angular templates, no TS info at all.
|
||||
// This effectively disables native TS features and is meant for internal
|
||||
// use only.
|
||||
const angularOnly = config ? config.angularOnly === true : false;
|
||||
const proxy: tss.LanguageService = Object.assign({}, tsLS);
|
||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||
const ngLS = createLanguageService(ngLSHost);
|
||||
projectHostMap.set(project, ngLSHost);
|
||||
|
||||
proxy.getCompletionsAtPosition = function(
|
||||
fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions|undefined) {
|
||||
let base = oldLS.getCompletionsAtPosition(fileName, position, options) || {
|
||||
fileName: string, position: number, options: tss.GetCompletionsAtPositionOptions|undefined) {
|
||||
if (!angularOnly) {
|
||||
const results = tsLS.getCompletionsAtPosition(fileName, position, options);
|
||||
if (results && results.entries.length) {
|
||||
// If TS could answer the query, then return results immediately.
|
||||
return results;
|
||||
}
|
||||
}
|
||||
const results = ngLS.getCompletionsAt(fileName, position);
|
||||
if (!results || !results.length) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: []
|
||||
entries: results.map(completionToEntry),
|
||||
};
|
||||
tryOperation('get completions', () => {
|
||||
const results = ls.getCompletionsAt(fileName, position);
|
||||
if (results && results.length) {
|
||||
if (base === undefined) {
|
||||
base = {
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: []
|
||||
};
|
||||
}
|
||||
for (const entry of results) {
|
||||
base.entries.push(completionToEntry(entry));
|
||||
}
|
||||
}
|
||||
});
|
||||
return base;
|
||||
};
|
||||
|
||||
proxy.getQuickInfoAtPosition = function(fileName: string, position: number): ts.QuickInfo |
|
||||
proxy.getQuickInfoAtPosition = function(fileName: string, position: number): tss.QuickInfo |
|
||||
undefined {
|
||||
const base = oldLS.getQuickInfoAtPosition(fileName, position);
|
||||
const ours = ls.getHoverAt(fileName, position);
|
||||
if (!ours) {
|
||||
return base;
|
||||
if (!angularOnly) {
|
||||
const result = tsLS.getQuickInfoAtPosition(fileName, position);
|
||||
if (result) {
|
||||
// If TS could answer the query, then return results immediately.
|
||||
return result;
|
||||
}
|
||||
const result: ts.QuickInfo = {
|
||||
}
|
||||
const result = ngLS.getHoverAt(fileName, position);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
// TODO(kyliau): Provide more useful info for kind and kindModifiers
|
||||
kind: ts.ScriptElementKind.unknown,
|
||||
kindModifiers: ts.ScriptElementKindModifier.none,
|
||||
textSpan: {
|
||||
start: ours.span.start,
|
||||
length: ours.span.end - ours.span.start,
|
||||
start: result.span.start,
|
||||
length: result.span.end - result.span.start,
|
||||
},
|
||||
displayParts: ours.text.map(part => {
|
||||
displayParts: result.text.map((part) => {
|
||||
return {
|
||||
text: part.text,
|
||||
kind: part.language || 'angular',
|
||||
};
|
||||
}),
|
||||
documentation: [],
|
||||
};
|
||||
if (base && base.tags) {
|
||||
result.tags = base.tags;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
proxy.getSemanticDiagnostics = function(fileName: string) {
|
||||
let result = oldLS.getSemanticDiagnostics(fileName);
|
||||
const base = result || [];
|
||||
tryOperation('get diagnostics', () => {
|
||||
logger.info(`Computing Angular semantic diagnostics...`);
|
||||
const ours = ls.getDiagnostics(fileName);
|
||||
if (ours && ours.length) {
|
||||
const file = oldLS.getProgram() !.getSourceFile(fileName);
|
||||
if (file) {
|
||||
base.push.apply(base, ours.map(d => diagnosticToDiagnostic(d, file)));
|
||||
proxy.getSemanticDiagnostics = function(fileName: string): tss.Diagnostic[] {
|
||||
const results: tss.Diagnostic[] = [];
|
||||
if (!angularOnly) {
|
||||
const tsResults = tsLS.getSemanticDiagnostics(fileName);
|
||||
results.push(...tsResults);
|
||||
}
|
||||
// For semantic diagnostics we need to combine both TS + Angular results
|
||||
const ngResults = ngLS.getDiagnostics(fileName);
|
||||
if (!ngResults.length) {
|
||||
return results;
|
||||
}
|
||||
});
|
||||
|
||||
return base;
|
||||
const sourceFile = fileName.endsWith('.ts') ? ngLSHost.getSourceFile(fileName) : undefined;
|
||||
results.push(...ngResults.map(d => diagnosticToDiagnostic(d, sourceFile)));
|
||||
return results;
|
||||
};
|
||||
|
||||
proxy.getDefinitionAtPosition = function(fileName: string, position: number):
|
||||
ReadonlyArray<ts.DefinitionInfo>|
|
||||
ReadonlyArray<tss.DefinitionInfo>|
|
||||
undefined {
|
||||
const base = oldLS.getDefinitionAtPosition(fileName, position);
|
||||
if (base && base.length) {
|
||||
return base;
|
||||
if (!angularOnly) {
|
||||
const results = tsLS.getDefinitionAtPosition(fileName, position);
|
||||
if (results) {
|
||||
// If TS could answer the query, then return results immediately.
|
||||
return results;
|
||||
}
|
||||
const ours = ls.getDefinitionAt(fileName, position);
|
||||
if (ours && ours.length) {
|
||||
return ours.map((loc: Location) => {
|
||||
return {
|
||||
fileName: loc.fileName,
|
||||
textSpan: {
|
||||
start: loc.span.start,
|
||||
length: loc.span.end - loc.span.start,
|
||||
},
|
||||
name: '',
|
||||
kind: ts.ScriptElementKind.unknown,
|
||||
containerName: loc.fileName,
|
||||
containerKind: ts.ScriptElementKind.unknown,
|
||||
};
|
||||
});
|
||||
}
|
||||
const results = ngLS.getDefinitionAt(fileName, position);
|
||||
if (!results) {
|
||||
return;
|
||||
}
|
||||
return results.map(ngLocationToTsDefinitionInfo);
|
||||
};
|
||||
|
||||
proxy.getDefinitionAndBoundSpan = function(fileName: string, position: number):
|
||||
ts.DefinitionInfoAndBoundSpan |
|
||||
tss.DefinitionInfoAndBoundSpan |
|
||||
undefined {
|
||||
const base = oldLS.getDefinitionAndBoundSpan(fileName, position);
|
||||
if (base && base.definitions && base.definitions.length) {
|
||||
return base;
|
||||
if (!angularOnly) {
|
||||
const result = tsLS.getDefinitionAndBoundSpan(fileName, position);
|
||||
if (result) {
|
||||
// If TS could answer the query, then return results immediately.
|
||||
return result;
|
||||
}
|
||||
const ours = ls.getDefinitionAt(fileName, position);
|
||||
if (ours && ours.length) {
|
||||
}
|
||||
const results = ngLS.getDefinitionAt(fileName, position);
|
||||
if (!results || !results.length) {
|
||||
return;
|
||||
}
|
||||
const {span} = results[0];
|
||||
return {
|
||||
definitions: ours.map((loc: Location) => {
|
||||
return {
|
||||
fileName: loc.fileName,
|
||||
definitions: results.map(ngLocationToTsDefinitionInfo),
|
||||
textSpan: {
|
||||
start: loc.span.start,
|
||||
length: loc.span.end - loc.span.start,
|
||||
},
|
||||
name: '',
|
||||
kind: ts.ScriptElementKind.unknown,
|
||||
containerName: loc.fileName,
|
||||
containerKind: ts.ScriptElementKind.unknown,
|
||||
};
|
||||
}),
|
||||
textSpan: {
|
||||
start: ours[0].span.start,
|
||||
length: ours[0].span.end - ours[0].span.start,
|
||||
start: span.start,
|
||||
length: span.end - span.start,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return proxy;
|
||||
|
@ -243,9 +243,9 @@ export interface Completion {
|
||||
/**
|
||||
* A sequence of completions.
|
||||
*
|
||||
* @publicApi
|
||||
* @deprecated
|
||||
*/
|
||||
export type Completions = Completion[] | undefined;
|
||||
export type Completions = Completion[];
|
||||
|
||||
/**
|
||||
* A file and span.
|
||||
@ -312,7 +312,7 @@ export interface Diagnostic {
|
||||
/**
|
||||
* A sequence of diagnostic message.
|
||||
*
|
||||
* @publicApi
|
||||
* @deprecated
|
||||
*/
|
||||
export type Diagnostics = Diagnostic[];
|
||||
|
||||
@ -384,17 +384,17 @@ export interface LanguageService {
|
||||
/**
|
||||
* Returns a list of all error for all templates in the given file.
|
||||
*/
|
||||
getDiagnostics(fileName: string): Diagnostics|undefined;
|
||||
getDiagnostics(fileName: string): Diagnostic[];
|
||||
|
||||
/**
|
||||
* Return the completions at the given position.
|
||||
*/
|
||||
getCompletionsAt(fileName: string, position: number): Completions|undefined;
|
||||
getCompletionsAt(fileName: string, position: number): Completion[]|undefined;
|
||||
|
||||
/**
|
||||
* Return the definition location for the symbol at position.
|
||||
*/
|
||||
getDefinitionAt(fileName: string, position: number): Definition|undefined;
|
||||
getDefinitionAt(fileName: string, position: number): Location[]|undefined;
|
||||
|
||||
/**
|
||||
* Return the hover information for the symbol at position.
|
||||
|
@ -117,22 +117,29 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
return this.templateReferences || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Angular template in the file, if any. If TS file is provided then
|
||||
* return the inline template, otherwise return the external template.
|
||||
* @param fileName Either TS or HTML file
|
||||
* @param position Only used if file is TS
|
||||
*/
|
||||
getTemplateAt(fileName: string, position: number): TemplateSource|undefined {
|
||||
let sourceFile = this.getSourceFile(fileName);
|
||||
if (fileName.endsWith('.ts')) {
|
||||
const sourceFile = this.getSourceFile(fileName);
|
||||
if (sourceFile) {
|
||||
this.context = sourceFile.fileName;
|
||||
let node = this.findNode(sourceFile, position);
|
||||
const node = this.findNode(sourceFile, position);
|
||||
if (node) {
|
||||
return this.getSourceFromNode(
|
||||
fileName, this.host.getScriptVersion(sourceFile.fileName), node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.ensureTemplateMap();
|
||||
// TODO: Cannocalize the file?
|
||||
const componentType = this.fileToComponent.get(fileName);
|
||||
if (componentType) {
|
||||
const componentSymbol = this.fileToComponent.get(fileName);
|
||||
if (componentSymbol) {
|
||||
return this.getSourceFromType(
|
||||
fileName, this.host.getScriptVersion(fileName), componentType);
|
||||
fileName, this.host.getScriptVersion(fileName), componentSymbol);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@ -164,14 +171,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
}
|
||||
|
||||
getTemplates(fileName: string): TemplateSources {
|
||||
this.ensureTemplateMap();
|
||||
const componentType = this.fileToComponent.get(fileName);
|
||||
if (componentType) {
|
||||
const templateSource = this.getTemplateAt(fileName, 0);
|
||||
if (templateSource) {
|
||||
return [templateSource];
|
||||
}
|
||||
} else {
|
||||
if (fileName.endsWith('.ts')) {
|
||||
let version = this.host.getScriptVersion(fileName);
|
||||
let result: TemplateSource[] = [];
|
||||
|
||||
@ -191,10 +191,22 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
ts.forEachChild(sourceFile, visit);
|
||||
}
|
||||
return result.length ? result : undefined;
|
||||
} else {
|
||||
this.ensureTemplateMap();
|
||||
const componentSymbol = this.fileToComponent.get(fileName);
|
||||
if (componentSymbol) {
|
||||
const templateSource = this.getTemplateAt(fileName, 0);
|
||||
if (templateSource) {
|
||||
return [templateSource];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDeclarations(fileName: string): Declarations {
|
||||
if (!fileName.endsWith('.ts')) {
|
||||
return [];
|
||||
}
|
||||
const result: Declarations = [];
|
||||
const sourceFile = this.getSourceFile(fileName);
|
||||
if (sourceFile) {
|
||||
@ -212,6 +224,9 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
}
|
||||
|
||||
getSourceFile(fileName: string): ts.SourceFile|undefined {
|
||||
if (!fileName.endsWith('.ts')) {
|
||||
throw new Error(`Non-TS source file requested: ${fileName}`);
|
||||
}
|
||||
return this.tsService.getProgram() !.getSourceFile(fileName);
|
||||
}
|
||||
|
||||
@ -383,7 +398,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
// The host's getCurrentDirectory() is not reliable as it is always "" in
|
||||
// tsserver. We don't need the exact base directory, just one that contains
|
||||
// a source file.
|
||||
const source = this.tsService.getProgram() !.getSourceFile(this.context);
|
||||
const source = this.getSourceFile(this.context);
|
||||
if (!source) {
|
||||
throw new Error('Internal error: no context could be determined');
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import 'reflect-metadata';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createLanguageService} from '../src/language_service';
|
||||
import {Completions} from '../src/types';
|
||||
import {Completion} from '../src/types';
|
||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
|
||||
import {toh} from './test_data';
|
||||
@ -228,7 +228,8 @@ export class MyComponent {
|
||||
});
|
||||
|
||||
|
||||
function expectEntries(locationMarker: string, completions: Completions, ...names: string[]) {
|
||||
function expectEntries(
|
||||
locationMarker: string, completions: Completion[] | undefined, ...names: string[]) {
|
||||
let entries: {[name: string]: boolean} = {};
|
||||
if (!completions) {
|
||||
throw new Error(
|
||||
|
@ -7,21 +7,16 @@
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {create} from '../src/ts_plugin';
|
||||
|
||||
import {toh} from './test_data';
|
||||
import {MockTypescriptHost} from './test_utils';
|
||||
|
||||
describe('plugin', () => {
|
||||
let documentRegistry = ts.createDocumentRegistry();
|
||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
let service = ts.createLanguageService(mockHost, documentRegistry);
|
||||
let program = service.getProgram();
|
||||
|
||||
const mockProject = {projectService: {logger: {info: function() {}}}};
|
||||
const mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
const service = ts.createLanguageService(mockHost);
|
||||
const program = service.getProgram();
|
||||
const plugin = createPlugin(service, mockHost);
|
||||
|
||||
it('should not report errors on tour of heroes', () => {
|
||||
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
|
||||
@ -31,15 +26,6 @@ describe('plugin', () => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let plugin = create({
|
||||
languageService: service,
|
||||
project: mockProject as any,
|
||||
languageServiceHost: mockHost,
|
||||
serverHost: {} as any,
|
||||
config: {},
|
||||
});
|
||||
|
||||
it('should not report template errors on tour of heroes', () => {
|
||||
for (let source of program !.getSourceFiles()) {
|
||||
// Ignore all 'cases.ts' files as they intentionally contain errors.
|
||||
@ -197,8 +183,54 @@ describe('plugin', () => {
|
||||
'implicit', 'The template context does not defined a member called \'unknown\'');
|
||||
});
|
||||
});
|
||||
|
||||
describe(`with config 'angularOnly = true`, () => {
|
||||
const ngLS = createPlugin(service, mockHost, {angularOnly: true});
|
||||
it('should not report template errors on TOH', () => {
|
||||
const sourceFiles = ngLS.getProgram() !.getSourceFiles();
|
||||
expect(sourceFiles.length).toBeGreaterThan(0);
|
||||
for (const {fileName} of sourceFiles) {
|
||||
// Ignore all 'cases.ts' files as they intentionally contain errors.
|
||||
if (!fileName.endsWith('cases.ts')) {
|
||||
expectNoDiagnostics(ngLS.getSemanticDiagnostics(fileName));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to get entity completions', () => {
|
||||
const fileName = 'app/app.component.ts';
|
||||
const marker = 'entity-amp';
|
||||
const position = getMarkerLocation(fileName, marker);
|
||||
const results = ngLS.getCompletionsAtPosition(fileName, position, {} /* options */);
|
||||
expect(results).toBeTruthy();
|
||||
expectEntries(marker, results !, ...['&', '>', '<', 'ι']);
|
||||
});
|
||||
|
||||
it('should report template diagnostics', () => {
|
||||
// TODO(kyliau): Rename these to end with '-error.ts'
|
||||
const fileName = 'app/expression-cases.ts';
|
||||
const diagnostics = ngLS.getSemanticDiagnostics(fileName);
|
||||
expect(diagnostics.map(d => d.messageText)).toEqual([
|
||||
`Identifier 'foo' is not defined. The component declaration, template variable declarations, and element references do not contain such a member`,
|
||||
`Identifier 'nam' is not defined. 'Person' does not contain such a member`,
|
||||
`Identifier 'myField' refers to a private member of the component`,
|
||||
`Expected a numeric type`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createPlugin(tsLS: ts.LanguageService, tsLSHost: ts.LanguageServiceHost, config = {}) {
|
||||
const project = {projectService: {logger: {info() {}}}};
|
||||
return create({
|
||||
languageService: tsLS,
|
||||
languageServiceHost: tsLSHost,
|
||||
project: project as any,
|
||||
serverHost: {} as any,
|
||||
config: {...config},
|
||||
});
|
||||
}
|
||||
|
||||
function getMarkerLocation(fileName: string, locationMarker: string): number {
|
||||
const location = mockHost.getMarkerLocations(fileName) ![locationMarker];
|
||||
if (location == null) {
|
||||
|
@ -15,51 +15,55 @@ import {toh} from './test_data';
|
||||
import {MockTypescriptHost} from './test_utils';
|
||||
|
||||
|
||||
describe('completions', () => {
|
||||
let host: ts.LanguageServiceHost;
|
||||
let service: ts.LanguageService;
|
||||
let ngHost: TypeScriptServiceHost;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockTypescriptHost(['/app/main.ts'], toh);
|
||||
service = ts.createLanguageService(host);
|
||||
describe('TypeScriptServiceHost', () => {
|
||||
it('should be able to create a typescript host', () => {
|
||||
const tsLSHost = new MockTypescriptHost(['/app/main.ts'], toh);
|
||||
const tsLS = ts.createLanguageService(tsLSHost);
|
||||
expect(() => new TypeScriptServiceHost(tsLSHost, tsLS)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should be able to create a typescript host',
|
||||
() => { expect(() => new TypeScriptServiceHost(host, service)).not.toThrow(); });
|
||||
it('should be able to analyze modules', () => {
|
||||
const tsLSHost = new MockTypescriptHost(['/app/main.ts'], toh);
|
||||
const tsLS = ts.createLanguageService(tsLSHost);
|
||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||
expect(ngLSHost.getAnalyzedModules()).toBeDefined();
|
||||
});
|
||||
|
||||
beforeEach(() => { ngHost = new TypeScriptServiceHost(host, service); });
|
||||
|
||||
it('should be able to analyze modules',
|
||||
() => { expect(ngHost.getAnalyzedModules()).toBeDefined(); });
|
||||
|
||||
it('should be able to analyze modules in without a tsconfig.json file', () => {
|
||||
host = new MockTypescriptHost(['foo.ts'], toh);
|
||||
service = ts.createLanguageService(host);
|
||||
ngHost = new TypeScriptServiceHost(host, service);
|
||||
expect(ngHost.getAnalyzedModules()).toBeDefined();
|
||||
it('should be able to analyze modules without a tsconfig.json file', () => {
|
||||
const tsLSHost = new MockTypescriptHost(['foo.ts'], toh);
|
||||
const tsLS = ts.createLanguageService(tsLSHost);
|
||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||
expect(ngLSHost.getAnalyzedModules()).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not throw if there is no script names', () => {
|
||||
host = new MockTypescriptHost([], toh);
|
||||
service = ts.createLanguageService(host);
|
||||
ngHost = new TypeScriptServiceHost(host, service);
|
||||
expect(() => ngHost.getAnalyzedModules()).not.toThrow();
|
||||
const tsLSHost = new MockTypescriptHost([], toh);
|
||||
const tsLS = ts.createLanguageService(tsLSHost);
|
||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||
expect(() => ngLSHost.getAnalyzedModules()).not.toThrow();
|
||||
});
|
||||
|
||||
it('should clear the caches if program changes', () => {
|
||||
// First create a TypescriptHost with empty script names
|
||||
host = new MockTypescriptHost([], toh);
|
||||
service = ts.createLanguageService(host);
|
||||
ngHost = new TypeScriptServiceHost(host, service);
|
||||
expect(ngHost.getAnalyzedModules().ngModules).toEqual([]);
|
||||
const tsLSHost = new MockTypescriptHost([], toh);
|
||||
const tsLS = ts.createLanguageService(tsLSHost);
|
||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||
expect(ngLSHost.getAnalyzedModules().ngModules).toEqual([]);
|
||||
// Now add a script, this would change the program
|
||||
const fileName = '/app/main.ts';
|
||||
const content = (host as MockTypescriptHost).getFileContent(fileName) !;
|
||||
(host as MockTypescriptHost).addScript(fileName, content);
|
||||
const content = (tsLSHost as MockTypescriptHost).getFileContent(fileName) !;
|
||||
(tsLSHost as MockTypescriptHost).addScript(fileName, content);
|
||||
// If the caches are not cleared, we would get back an empty array.
|
||||
// But if the caches are cleared then the analyzed modules will be non-empty.
|
||||
expect(ngHost.getAnalyzedModules().ngModules.length).not.toEqual(0);
|
||||
expect(ngLSHost.getAnalyzedModules().ngModules.length).not.toEqual(0);
|
||||
});
|
||||
|
||||
it('should throw if getSourceFile is called on non-TS file', () => {
|
||||
const tsLSHost = new MockTypescriptHost([], toh);
|
||||
const tsLS = ts.createLanguageService(tsLSHost);
|
||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||
expect(() => {
|
||||
ngLSHost.getSourceFile('/src/test.ng');
|
||||
}).toThrowError('Non-TS source file requested: /src/test.ng');
|
||||
});
|
||||
});
|
||||
|
@ -49,12 +49,20 @@ export function flattenStyles(
|
||||
|
||||
function decoratePreventDefault(eventHandler: Function): Function {
|
||||
return (event: any) => {
|
||||
// Ivy uses `Function` as a special token that allows us to unwrap the function
|
||||
// so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
|
||||
if (event === Function) {
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
const allowDefaultBehavior = eventHandler(event);
|
||||
if (allowDefaultBehavior === false) {
|
||||
// TODO(tbosch): move preventDefault into event plugins...
|
||||
event.preventDefault();
|
||||
event.returnValue = false;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -182,6 +182,12 @@ class DefaultServerRenderer2 implements Renderer2 {
|
||||
|
||||
private decoratePreventDefault(eventHandler: Function): Function {
|
||||
return (event: any) => {
|
||||
// Ivy uses `Function` as a special token that allows us to unwrap the function
|
||||
// so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
|
||||
if (event === Function) {
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
// Run the event handler inside the ngZone because event handlers are not patched
|
||||
// by Zone on the server. This is required only for tests.
|
||||
const allowDefaultBehavior = this.ngZone.runGuarded(() => eventHandler(event));
|
||||
@ -189,6 +195,8 @@ class DefaultServerRenderer2 implements Renderer2 {
|
||||
event.preventDefault();
|
||||
event.returnValue = false;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ export type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParam
|
||||
* and both of them require an ID parameter. You can accomplish this using a route
|
||||
* that does not specify a component at the top level.
|
||||
*
|
||||
* In the following example, 'MainChild' and 'AuxChild' are siblings.
|
||||
* In the following example, 'ChildCmp' and 'AuxCmp' are siblings.
|
||||
* When navigating to 'parent/10/(a//aux:b)', the route instantiates
|
||||
* the main child and aux child components next to each other.
|
||||
* For this to work, the application component must have the primary and aux outlets defined.
|
||||
|
@ -196,11 +196,7 @@ export function downgradeComponent(info: {
|
||||
wrapCallback(() => doDowngrade(pInjector, mInjector))();
|
||||
};
|
||||
|
||||
// NOTE:
|
||||
// Not using `ParentInjectorPromise.all()` (which is inherited from `SyncPromise`), because
|
||||
// Closure Compiler (or some related tool) complains:
|
||||
// `TypeError: ...$src$downgrade_component_ParentInjectorPromise.all is not a function`
|
||||
SyncPromise.all([finalParentInjector, finalModuleInjector])
|
||||
ParentInjectorPromise.all([finalParentInjector, finalModuleInjector])
|
||||
.then(([pInjector, mInjector]) => downgradeFn(pInjector, mInjector));
|
||||
|
||||
ranAsync = true;
|
||||
|
8
packages/zone.js/dist/BUILD.bazel
vendored
8
packages/zone.js/dist/BUILD.bazel
vendored
@ -100,7 +100,9 @@ genrule(
|
||||
],
|
||||
cmd = " && ".join([
|
||||
"mkdir -p $(@D)",
|
||||
"cp $(@D)/" + b[0].replace("-", "_") + "_rollup.es5umd.js $(@D)/" + b[0] + ".js",
|
||||
# remove the last line '//# sourceMappingURL=b[0].umd.js.map' because we don't release
|
||||
# source map for now
|
||||
"sed '$$d' $(@D)/" + b[0].replace("-", "_") + "_rollup.es5umd.js > $(@D)/" + b[0] + ".js",
|
||||
"cp $(@D)/" + b[0].replace("-", "_") + "_rollup.min.es5umd.js $(@D)/" + b[0] + ".min.js",
|
||||
]),
|
||||
)
|
||||
@ -174,7 +176,9 @@ genrule(
|
||||
],
|
||||
cmd = " && ".join([
|
||||
"mkdir -p $(@D)",
|
||||
"cp $(@D)/" + b.replace("-", "_") + "_rollup.umd.js $(@D)/" + b + ".js",
|
||||
# remove the last line '//# sourceMappingURL=b[0].umd.js.map' because we don't release
|
||||
# source map for now
|
||||
"sed '$$d' $(@D)/" + b.replace("-", "_") + "_rollup.umd.js > $(@D)/" + b + ".js",
|
||||
"cp $(@D)/" + b.replace("-", "_") + "_rollup.min.umd.js $(@D)/" + b + ".min.js",
|
||||
]),
|
||||
)
|
||||
|
@ -49,11 +49,16 @@ describe('Zone.js npm_package', () => {
|
||||
describe('es5', () => {
|
||||
it('zone.js(es5) should not contain es6 spread code',
|
||||
() => { expect(shx.cat('zone.js')).not.toContain('let value of values'); });
|
||||
|
||||
it('zone.js(es5) should not contain source map comment',
|
||||
() => { expect(shx.cat('zone.js')).not.toContain('sourceMappingURL'); });
|
||||
});
|
||||
|
||||
describe('es2015', () => {
|
||||
it('zone-evergreen.js(es2015) should contain es6 code',
|
||||
() => { expect(shx.cat('zone-evergreen.js')).toContain('let value of values'); });
|
||||
it('zone.js(es5) should not contain source map comment',
|
||||
() => { expect(shx.cat('zone-evergreen.js')).not.toContain('sourceMappingURL'); });
|
||||
});
|
||||
|
||||
describe('dist file list', () => {
|
||||
|
Reference in New Issue
Block a user