Compare commits
134 Commits
8.2.2
...
9.0.0-next
Author | SHA1 | Date | |
---|---|---|---|
9406104c0a | |||
c0194e0115 | |||
914900a561 | |||
4ea3e7e000 | |||
6eb9c2fab0 | |||
7b9414565e | |||
37de490e23 | |||
753080133b | |||
9a37e827e2 | |||
3df54be9e4 | |||
c0d5684078 | |||
2b289250d8 | |||
ddd02044ea | |||
c198a27a3c | |||
4f37487b1c | |||
0ddf0c4895 | |||
fd6ed1713d | |||
eb5412d76f | |||
7533338362 | |||
9896d438c0 | |||
684579b338 | |||
695f322dc1 | |||
f90c7a9df0 | |||
f2466cf4ee | |||
bed680cff8 | |||
a647298412 | |||
2abbe98e33 | |||
7613f13e54 | |||
4b8cdd4b57 | |||
17e289c39f | |||
2913340af7 | |||
f8c27d42ed | |||
17bb633031 | |||
9106271f2c | |||
48a3741d5a | |||
9d1f43f3ba | |||
6f98107d5e | |||
a8e2ee1343 | |||
e906a4f0d8 | |||
b5b33d12d6 | |||
22d3cabc10 | |||
a06043b703 | |||
4689ea2727 | |||
939529ce5d | |||
46304a4f83 | |||
f7eebd0227 | |||
8af2cc1efe | |||
e5a89e047c | |||
29d3b68554 | |||
93d27eefd5 | |||
ed70f73794 | |||
ef12e10e59 | |||
2954d1b5ca | |||
3077c9a1f8 | |||
9537b2ff84 | |||
961d663fbe | |||
57e15fc08b | |||
b70746a113 | |||
0709ed4c2b | |||
fa699f65d7 | |||
18bc4eda9f | |||
f542649b2b | |||
a574e462c9 | |||
65cafa0eec | |||
18aa173d39 | |||
bc8eb8508b | |||
7db269ba6a | |||
8e5567d964 | |||
541ce98432 | |||
e7e3f5d952 | |||
382d3ed1d2 | |||
a07de82f79 | |||
2e84f4e0cd | |||
a5b12db7d6 | |||
8b94d6a402 | |||
96cbcd6da4 | |||
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 |
120
CHANGELOG.md
120
CHANGELOG.md
@ -1,3 +1,21 @@
|
||||
<a name="9.0.0-next.2"></a>
|
||||
# [9.0.0-next.2](https://github.com/angular/angular/compare/9.0.0-next.1...9.0.0-next.2) (2019-08-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** disable treeshaking when generating FESM and UMD bundles ([#32069](https://github.com/angular/angular/issues/32069)) ([4f37487](https://github.com/angular/angular/commit/4f37487))
|
||||
* **compiler:** do not remove whitespace wrapping i18n expansions ([#31962](https://github.com/angular/angular/issues/31962)) ([0ddf0c4](https://github.com/angular/angular/commit/0ddf0c4))
|
||||
* **ivy:** reuse compilation scope for incremental template changes. ([#31932](https://github.com/angular/angular/issues/31932)) ([eb5412d](https://github.com/angular/angular/commit/eb5412d)), closes [#31654](https://github.com/angular/angular/issues/31654)
|
||||
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ivy:** don't read global state when interpolated values don't change ([#32093](https://github.com/angular/angular/issues/32093)) ([6eb9c2f](https://github.com/angular/angular/commit/6eb9c2f))
|
||||
|
||||
|
||||
|
||||
<a name="8.2.2"></a>
|
||||
## [8.2.2](https://github.com/angular/angular/compare/8.2.1...8.2.2) (2019-08-12)
|
||||
|
||||
@ -7,6 +25,18 @@
|
||||
* **bazel:** disable treeshaking when generating FESM and UMD bundles ([#32069](https://github.com/angular/angular/issues/32069)) ([3420d29](https://github.com/angular/angular/commit/3420d29))
|
||||
|
||||
|
||||
<a name="9.0.0-next.1"></a>
|
||||
# [9.0.0-next.1](https://github.com/angular/angular/compare/9.0.0-next.0...9.0.0-next.1) (2019-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **language-service:** getSourceFile() should only be called on TS files ([#31920](https://github.com/angular/angular/issues/31920)) ([e8b8f6d](https://github.com/angular/angular/commit/e8b8f6d))
|
||||
* **language-service:** Make Definition and QuickInfo compatible with TS LS ([#31972](https://github.com/angular/angular/issues/31972)) ([a8e2ee1](https://github.com/angular/angular/commit/a8e2ee1))
|
||||
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([c1ae612](https://github.com/angular/angular/commit/c1ae612)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
|
||||
|
||||
|
||||
|
||||
<a name="8.2.1"></a>
|
||||
## [8.2.1](https://github.com/angular/angular/compare/8.2.0...8.2.1) (2019-08-08)
|
||||
|
||||
@ -17,38 +47,47 @@
|
||||
|
||||
|
||||
|
||||
<a name="9.0.0-next.0"></a>
|
||||
# [9.0.0-next.0](https://github.com/angular/angular/compare/8.2.0-next.2...9.0.0-next.0) (2019-07-31)
|
||||
|
||||
* Ivy related improvements and fixes
|
||||
|
||||
|
||||
<a name="8.2.0"></a>
|
||||
# [8.2.0](https://github.com/angular/angular/compare/8.2.0-rc.0...8.2.0) (2019-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** DebugElement.listeners not cleared on destroy ([#31820](https://github.com/angular/angular/issues/31820)) ([46b160e](https://github.com/angular/angular/commit/46b160e))
|
||||
|
||||
|
||||
|
||||
<a name="8.2.0-rc.0"></a>
|
||||
# [8.2.0-rc.0](https://github.com/angular/angular/compare/8.2.0-next.2...8.2.0-rc.0) (2019-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** increase memory limit of ngc under bazel from 2 to 4 GB ([#31784](https://github.com/angular/angular/issues/31784)) ([5a8eb92](https://github.com/angular/angular/commit/5a8eb92))
|
||||
* **core:** allow Z variations of CSS transforms in sanitizer ([#29264](https://github.com/angular/angular/issues/29264)) ([78e7fdd](https://github.com/angular/angular/commit/78e7fdd))
|
||||
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([7151eae](https://github.com/angular/angular/commit/7151eae)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
|
||||
* **platform-browser:** debug element query predicates not compatible with strictFunctionTypes ([#30993](https://github.com/angular/angular/issues/30993)) ([10a1e19](https://github.com/angular/angular/commit/10a1e19))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db](https://github.com/angular/angular/commit/6ece7db))
|
||||
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
|
||||
* **bazel:** compile targets used for indexing by Kythe with Ivy ([#31786](https://github.com/angular/angular/issues/31786)) ([82055b2](https://github.com/angular/angular/commit/82055b2))
|
||||
* **upgrade:** support $element in upgraded component template/templateUrl functions ([#31637](https://github.com/angular/angular/issues/31637)) ([29e1c53](https://github.com/angular/angular/commit/29e1c53))
|
||||
* **bazel:** allow passing a custom bazel compiler host to ngc compile ([#31341](https://github.com/angular/angular/issues/31341)) ([a29dc96](https://github.com/angular/angular/commit/a29dc96))
|
||||
* **bazel:** allow passing and rewriting an old bazel host ([#31381](https://github.com/angular/angular/issues/31381)) ([11a208f](https://github.com/angular/angular/commit/11a208f)), closes [#31341](https://github.com/angular/angular/issues/31341)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([24ca582](https://github.com/angular/angular/commit/24ca582)), closes [#31627](https://github.com/angular/angular/issues/31627)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** DebugElement.listeners not cleared on destroy ([#31820](https://github.com/angular/angular/issues/31820)) ([46b160e](https://github.com/angular/angular/commit/46b160e))
|
||||
* **bazel:** increase memory limit of ngc under bazel from 2 to 4 GB ([#31784](https://github.com/angular/angular/issues/31784)) ([5a8eb92](https://github.com/angular/angular/commit/5a8eb92))
|
||||
* **core:** allow Z variations of CSS transforms in sanitizer ([#29264](https://github.com/angular/angular/issues/29264)) ([78e7fdd](https://github.com/angular/angular/commit/78e7fdd))
|
||||
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([7151eae](https://github.com/angular/angular/commit/7151eae)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
|
||||
* **platform-browser:** debug element query predicates not compatible with strictFunctionTypes ([#30993](https://github.com/angular/angular/issues/30993)) ([10a1e19](https://github.com/angular/angular/commit/10a1e19))
|
||||
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([9204de9](https://github.com/angular/angular/commit/9204de9))
|
||||
* **bazel:** pass custom bazel compiler host rather than rewriting one ([#31496](https://github.com/angular/angular/issues/31496)) ([0c61a35](https://github.com/angular/angular/commit/0c61a35))
|
||||
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([3166cff](https://github.com/angular/angular/commit/3166cff)), closes [#22524](https://github.com/angular/angular/issues/22524)
|
||||
* **language-service:** Eagarly initialize data members ([#31577](https://github.com/angular/angular/issues/31577)) ([0110de2](https://github.com/angular/angular/commit/0110de2))
|
||||
* **bazel:** revert location of xi18n outputs to bazel-genfiles ([#31410](https://github.com/angular/angular/issues/31410)) ([1d3e227](https://github.com/angular/angular/commit/1d3e227))
|
||||
* **compiler:** give ASTWithSource its own visit method ([#31347](https://github.com/angular/angular/issues/31347)) ([6aaca21](https://github.com/angular/angular/commit/6aaca21))
|
||||
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
|
||||
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([d7be38f](https://github.com/angular/angular/commit/d7be38f)), closes [#30968](https://github.com/angular/angular/issues/30968)
|
||||
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([93abc35](https://github.com/angular/angular/commit/93abc35))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.3"></a>
|
||||
@ -65,17 +104,6 @@
|
||||
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([1f3daa0](https://github.com/angular/angular/commit/1f3daa0)), closes [#31627](https://github.com/angular/angular/issues/31627)
|
||||
|
||||
|
||||
<a name="8.2.0-next.2"></a>
|
||||
# [8.2.0-next.2](https://github.com/angular/angular/compare/8.2.0-next.1...8.2.0-next.2) (2019-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([9204de9](https://github.com/angular/angular/commit/9204de9))
|
||||
* **bazel:** pass custom bazel compiler host rather than rewriting one ([#31496](https://github.com/angular/angular/issues/31496)) ([0c61a35](https://github.com/angular/angular/commit/0c61a35))
|
||||
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([3166cff](https://github.com/angular/angular/commit/3166cff)), closes [#22524](https://github.com/angular/angular/issues/22524)
|
||||
* **language-service:** Eagarly initialize data members ([#31577](https://github.com/angular/angular/issues/31577)) ([0110de2](https://github.com/angular/angular/commit/0110de2))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.2"></a>
|
||||
@ -89,23 +117,6 @@
|
||||
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
|
||||
|
||||
|
||||
|
||||
<a name="8.2.0-next.1"></a>
|
||||
# [8.2.0-next.1](https://github.com/angular/angular/compare/8.2.0-next.0...8.2.0-next.1) (2019-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** revert location of xi18n outputs to bazel-genfiles ([#31410](https://github.com/angular/angular/issues/31410)) ([1d3e227](https://github.com/angular/angular/commit/1d3e227))
|
||||
* **compiler:** give ASTWithSource its own visit method ([#31347](https://github.com/angular/angular/issues/31347)) ([6aaca21](https://github.com/angular/angular/commit/6aaca21))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.1"></a>
|
||||
## [8.1.1](https://github.com/angular/angular/compare/8.1.0...8.1.1) (2019-07-10)
|
||||
|
||||
@ -115,23 +126,6 @@
|
||||
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
|
||||
|
||||
|
||||
<a name="8.2.0-next.0"></a>
|
||||
# [8.2.0-next.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.2.0-next.0) (2019-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
|
||||
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([d7be38f](https://github.com/angular/angular/commit/d7be38f)), closes [#30968](https://github.com/angular/angular/issues/30968)
|
||||
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([93abc35](https://github.com/angular/angular/commit/93abc35))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** allow passing a custom bazel compiler host to ngc compile ([#31341](https://github.com/angular/angular/issues/31341)) ([a29dc96](https://github.com/angular/angular/commit/a29dc96))
|
||||
* **bazel:** allow passing and rewriting an old bazel host ([#31381](https://github.com/angular/angular/issues/31381)) ([11a208f](https://github.com/angular/angular/commit/11a208f)), closes [#31341](https://github.com/angular/angular/issues/31341)
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0"></a>
|
||||
# [8.1.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.1.0) (2019-07-02)
|
||||
|
@ -23,7 +23,7 @@
|
||||
"build-with-ivy": "yarn ~~build",
|
||||
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
|
||||
"build-with-ivy-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js de49294bf",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js bb4be27da",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
|
@ -28,11 +28,11 @@
|
||||
"uncompressed": {
|
||||
"runtime-es5": 2932,
|
||||
"runtime-es2015": 2938,
|
||||
"main-es5": 555102,
|
||||
"main-es2015": 572938,
|
||||
"main-es5": 560811,
|
||||
"main-es2015": 499846,
|
||||
"polyfills-es5": 129161,
|
||||
"polyfills-es2015": 53295
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1440,
|
||||
"main": 14021,
|
||||
"main": 13517,
|
||||
"polyfills": 43567
|
||||
}
|
||||
}
|
||||
@ -34,4 +34,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "file:../../node_modules/protractor",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"tslint": "~5.18.0",
|
||||
"typescript": "file:../../node_modules/typescript"
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "file:../../node_modules/protractor",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"tslint": "~5.18.0",
|
||||
"typescript": "file:../../node_modules/typescript"
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "file:../../node_modules/protractor",
|
||||
"ts-node": "~4.1.0",
|
||||
"tslint": "~5.13.0",
|
||||
"tslint": "~5.18.0",
|
||||
"typescript": "file:../../node_modules/typescript"
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,12 @@
|
||||
],
|
||||
"textSpan": {
|
||||
"start": {
|
||||
"line": 7,
|
||||
"offset": 30
|
||||
"line": 5,
|
||||
"offset": 26
|
||||
},
|
||||
"end": {
|
||||
"line": 7,
|
||||
"offset": 47
|
||||
"line": 5,
|
||||
"offset": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"request_seq": 2,
|
||||
"success": true,
|
||||
"body": {
|
||||
"kind": "",
|
||||
"kind": "property",
|
||||
"kindModifiers": "",
|
||||
"start": {
|
||||
"line": 5,
|
||||
@ -15,7 +15,7 @@
|
||||
"line": 5,
|
||||
"offset": 30
|
||||
},
|
||||
"displayString": "property name of AppComponent",
|
||||
"displayString": "(property) AppComponent.name",
|
||||
"documentation": "",
|
||||
"tags": []
|
||||
}
|
||||
|
@ -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.2",
|
||||
"version": "9.0.0-next.2",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
|
@ -75,7 +75,7 @@ export class DecorationAnalyzer {
|
||||
new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator, this.isCore),
|
||||
new ComponentDecoratorHandler(
|
||||
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
|
||||
this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
|
||||
this.scopeRegistry, this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
|
||||
/* defaultPreserveWhitespaces */ false,
|
||||
/* i18nUseExternalIds */ true, this.moduleResolver, this.cycleAnalyzer, this.refEmitter,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER),
|
||||
|
@ -9,9 +9,11 @@
|
||||
import {DepGraph} from 'dependency-graph';
|
||||
import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from '../packages/entry_point';
|
||||
import {DependencyHost, DependencyInfo} from './dependency_host';
|
||||
|
||||
const builtinNodeJsModules = new Set<string>(require('module').builtinModules);
|
||||
|
||||
/**
|
||||
* Holds information about entry points that are removed because
|
||||
* they have dependencies that are missing (directly or transitively).
|
||||
@ -81,7 +83,7 @@ export class DependencyResolver {
|
||||
|
||||
let sortedEntryPointNodes: string[];
|
||||
if (target) {
|
||||
if (target.compiledByAngular) {
|
||||
if (target.compiledByAngular && graph.hasNode(target.path)) {
|
||||
sortedEntryPointNodes = graph.dependenciesOf(target.path);
|
||||
sortedEntryPointNodes.push(target.path);
|
||||
} else {
|
||||
@ -128,10 +130,12 @@ export class DependencyResolver {
|
||||
angularEntryPoints.forEach(entryPoint => {
|
||||
const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);
|
||||
|
||||
if (missing.size > 0) {
|
||||
const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep));
|
||||
|
||||
if (missingDependencies.length > 0) {
|
||||
// This entry point has dependencies that are missing
|
||||
// so remove it from the graph.
|
||||
removeNodes(entryPoint, Array.from(missing));
|
||||
removeNodes(entryPoint, missingDependencies);
|
||||
} else {
|
||||
dependencies.forEach(dependencyPath => {
|
||||
if (!graph.hasNode(entryPoint.path)) {
|
||||
@ -173,16 +177,16 @@ export class DependencyResolver {
|
||||
|
||||
private getEntryPointFormatInfo(entryPoint: EntryPoint):
|
||||
{format: EntryPointFormat, path: AbsoluteFsPath} {
|
||||
const properties = Object.keys(entryPoint.packageJson);
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const property = properties[i] as EntryPointJsonProperty;
|
||||
const format = getEntryPointFormat(this.fs, entryPoint, property);
|
||||
for (const property of SUPPORTED_FORMAT_PROPERTIES) {
|
||||
const formatPath = entryPoint.packageJson[property];
|
||||
if (formatPath === undefined) continue;
|
||||
|
||||
if (format === 'esm2015' || format === 'esm5' || format === 'umd' || format === 'commonjs') {
|
||||
const formatPath = entryPoint.packageJson[property] !;
|
||||
return {format, path: resolve(entryPoint.path, formatPath)};
|
||||
}
|
||||
const format = getEntryPointFormat(this.fs, entryPoint, property);
|
||||
if (format === undefined) continue;
|
||||
|
||||
return {format, path: resolve(entryPoint.path, formatPath)};
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
|
||||
}
|
||||
|
74
packages/compiler-cli/ngcc/src/execution/api.ts
Normal file
74
packages/compiler-cli/ngcc/src/execution/api.ts
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
|
||||
|
||||
/** The type of the function that analyzes entry-points and creates the list of tasks. */
|
||||
export type AnalyzeFn = () => {
|
||||
processingMetadataPerEntryPoint: Map<string, EntryPointProcessingMetadata>;
|
||||
tasks: Task[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the function that creates the `compile()` function, which in turn can be used to
|
||||
* process tasks.
|
||||
*/
|
||||
export type CreateCompileFn =
|
||||
(onTaskCompleted: (task: Task, outcome: TaskProcessingOutcome) => void) => (task: Task) => void;
|
||||
|
||||
/**
|
||||
* The type of the function that orchestrates and executes the required work (i.e. analyzes the
|
||||
* entry-points, processes the resulting tasks, does book-keeping and validates the final outcome).
|
||||
*/
|
||||
export type ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => void;
|
||||
|
||||
/** Represents metadata related to the processing of an entry-point. */
|
||||
export interface EntryPointProcessingMetadata {
|
||||
/**
|
||||
* Whether the typings for the entry-point have been successfully processed (or were already
|
||||
* processed).
|
||||
*/
|
||||
hasProcessedTypings: boolean;
|
||||
|
||||
/**
|
||||
* Whether at least one format has been successfully processed (or was already processed) for the
|
||||
* entry-point.
|
||||
*/
|
||||
hasAnyProcessedFormat: boolean;
|
||||
}
|
||||
|
||||
/** Represents a unit of work: processing a specific format property of an entry-point. */
|
||||
export interface Task {
|
||||
/** The `EntryPoint` which needs to be processed as part of the task. */
|
||||
entryPoint: EntryPoint;
|
||||
|
||||
/**
|
||||
* The `package.json` format property to process (i.e. the property which points to the file that
|
||||
* is the program entry-point).
|
||||
*/
|
||||
formatProperty: EntryPointJsonProperty;
|
||||
|
||||
/**
|
||||
* The list of all format properties (including `task.formatProperty`) that should be marked as
|
||||
* processed once the taksk has been completed, because they point to the format-path that will be
|
||||
* processed as part of the task.
|
||||
*/
|
||||
formatPropertiesToMarkAsProcessed: EntryPointJsonProperty[];
|
||||
|
||||
/** Whether to also process typings for this entry-point as part of the task. */
|
||||
processDts: boolean;
|
||||
}
|
||||
|
||||
/** Represents the outcome of processing a `Task`. */
|
||||
export const enum TaskProcessingOutcome {
|
||||
/** The target format property was already processed - didn't have to do anything. */
|
||||
AlreadyProcessed,
|
||||
|
||||
/** Successfully processed the target format property. */
|
||||
Processed,
|
||||
}
|
@ -9,10 +9,10 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, Import, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {BundleProgram} from '../packages/bundle_program';
|
||||
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
|
||||
import {findAll, getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils';
|
||||
|
||||
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||
|
||||
@ -73,6 +73,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
*/
|
||||
protected aliasedClassDeclarations = new Map<ts.Declaration, ts.Identifier>();
|
||||
|
||||
/**
|
||||
* Caches the information of the decorators on a class, as the work involved with extracting
|
||||
* decorators is complex and frequently used.
|
||||
*
|
||||
* This map is lazily populated during the first call to `acquireDecoratorInfo` for a given class.
|
||||
*/
|
||||
protected decoratorCache = new Map<ClassDeclaration, DecoratorInfo>();
|
||||
|
||||
constructor(
|
||||
protected logger: Logger, protected isCore: boolean, checker: ts.TypeChecker,
|
||||
dts?: BundleProgram|null) {
|
||||
@ -247,12 +255,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
|
||||
/** Gets all decorators of the given class symbol. */
|
||||
getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
|
||||
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
|
||||
if (decoratorsProperty) {
|
||||
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
} else {
|
||||
return this.getClassDecoratorsFromHelperCall(symbol);
|
||||
}
|
||||
const {classDecorators} = this.acquireDecoratorInfo(symbol);
|
||||
return classDecorators;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -542,6 +546,72 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return symbol.exports && symbol.exports.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main entry-point for obtaining information on the decorators of a given class. This
|
||||
* information is computed either from static properties if present, or using `tslib.__decorate`
|
||||
* helper calls otherwise. The computed result is cached per class.
|
||||
*
|
||||
* @param classSymbol the class for which decorators should be acquired.
|
||||
* @returns all information of the decorators on the class.
|
||||
*/
|
||||
protected acquireDecoratorInfo(classSymbol: ClassSymbol): DecoratorInfo {
|
||||
if (this.decoratorCache.has(classSymbol.valueDeclaration)) {
|
||||
return this.decoratorCache.get(classSymbol.valueDeclaration) !;
|
||||
}
|
||||
|
||||
// First attempt extracting decorators from static properties.
|
||||
let decoratorInfo = this.computeDecoratorInfoFromStaticProperties(classSymbol);
|
||||
if (decoratorInfo === null) {
|
||||
// If none were present, use the `__decorate` helper calls instead.
|
||||
decoratorInfo = this.computeDecoratorInfoFromHelperCalls(classSymbol);
|
||||
}
|
||||
|
||||
this.decoratorCache.set(classSymbol.valueDeclaration, decoratorInfo);
|
||||
return decoratorInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to compute decorator information from static properties "decorators", "propDecorators"
|
||||
* and "ctorParameters" on the class. If neither of these static properties is present the
|
||||
* library is likely not compiled using tsickle for usage with Closure compiler, in which case
|
||||
* `null` is returned.
|
||||
*
|
||||
* @param classSymbol The class symbol to compute the decorators information for.
|
||||
* @returns All information on the decorators as extracted from static properties, or `null` if
|
||||
* none of the static properties exist.
|
||||
*/
|
||||
protected computeDecoratorInfoFromStaticProperties(classSymbol: ClassSymbol): DecoratorInfo|null {
|
||||
let classDecorators: Decorator[]|null = null;
|
||||
let memberDecorators: Map<string, Decorator[]>|null = null;
|
||||
let constructorParamInfo: ParamInfo[]|null = null;
|
||||
|
||||
const decoratorsProperty = this.getStaticProperty(classSymbol, DECORATORS);
|
||||
if (decoratorsProperty !== undefined) {
|
||||
classDecorators = this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
}
|
||||
|
||||
const propDecoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS);
|
||||
if (propDecoratorsProperty !== undefined) {
|
||||
memberDecorators = this.getMemberDecoratorsFromStaticProperty(propDecoratorsProperty);
|
||||
}
|
||||
|
||||
const constructorParamsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS);
|
||||
if (constructorParamsProperty !== undefined) {
|
||||
constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty);
|
||||
}
|
||||
|
||||
// If none of the static properties were present, no decorator info could be computed.
|
||||
if (classDecorators === null && memberDecorators === null && constructorParamInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
classDecorators,
|
||||
memberDecorators: memberDecorators || new Map<string, Decorator[]>(),
|
||||
constructorParamInfo: constructorParamInfo || [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all class decorators for the given class, where the decorators are declared
|
||||
* via a static property. For example:
|
||||
@ -570,32 +640,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all class decorators for the given class, where the decorators are declared
|
||||
* via the `__decorate` helper method. For example:
|
||||
*
|
||||
* ```
|
||||
* let SomeDirective = class SomeDirective {}
|
||||
* SomeDirective = __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* @param symbol the class whose decorators we want to get.
|
||||
* @returns an array of decorators or null if none where found.
|
||||
*/
|
||||
protected getClassDecoratorsFromHelperCall(symbol: ClassSymbol): Decorator[]|null {
|
||||
const decorators: Decorator[] = [];
|
||||
const helperCalls = this.getHelperCallsForClass(symbol, '__decorate');
|
||||
helperCalls.forEach(helperCall => {
|
||||
const {classDecorators} =
|
||||
this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(symbol.name));
|
||||
classDecorators.filter(decorator => this.isFromCore(decorator))
|
||||
.forEach(decorator => decorators.push(decorator));
|
||||
});
|
||||
return decorators.length ? decorators : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a symbol which should be of a class, and return metadata about its members.
|
||||
*
|
||||
@ -606,7 +650,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
const members: ClassMember[] = [];
|
||||
|
||||
// The decorators map contains all the properties that are decorated
|
||||
const decoratorsMap = this.getMemberDecorators(symbol);
|
||||
const {memberDecorators} = this.acquireDecoratorInfo(symbol);
|
||||
|
||||
// Make a copy of the decorators as successfully reflected members delete themselves from the
|
||||
// map, so that any leftovers can be easily dealt with.
|
||||
const decoratorsMap = new Map(memberDecorators);
|
||||
|
||||
// The member map contains all the method (instance and static); and any instance properties
|
||||
// that are initialized in the class.
|
||||
@ -675,21 +723,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the member decorators for the given class.
|
||||
* @param classSymbol the class whose member decorators we are interested in.
|
||||
* @returns a map whose keys are the name of the members and whose values are collections of
|
||||
* decorators for the given member.
|
||||
*/
|
||||
protected getMemberDecorators(classSymbol: ClassSymbol): Map<string, Decorator[]> {
|
||||
const decoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS);
|
||||
if (decoratorsProperty) {
|
||||
return this.getMemberDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
} else {
|
||||
return this.getMemberDecoratorsFromHelperCalls(classSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Member decorators may be declared as static properties of the class:
|
||||
*
|
||||
@ -724,7 +757,21 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
}
|
||||
|
||||
/**
|
||||
* Member decorators may be declared via helper call statements.
|
||||
* For a given class symbol, collects all decorator information from tslib helper methods, as
|
||||
* generated by TypeScript into emitted JavaScript files.
|
||||
*
|
||||
* Class decorators are extracted from calls to `tslib.__decorate` that look as follows:
|
||||
*
|
||||
* ```
|
||||
* let SomeDirective = class SomeDirective {}
|
||||
* SomeDirective = __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* The extraction of member decorators is similar, with the distinction that its 2nd and 3rd
|
||||
* argument correspond with a "prototype" target and the name of the member to which the
|
||||
* decorators apply.
|
||||
*
|
||||
* ```
|
||||
* __decorate([
|
||||
@ -733,102 +780,188 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* ], SomeDirective.prototype, "input1", void 0);
|
||||
* ```
|
||||
*
|
||||
* @param classSymbol the class whose member decorators we are interested in.
|
||||
* @returns a map whose keys are the name of the members and whose values are collections of
|
||||
* decorators for the given member.
|
||||
* @param classSymbol The class symbol for which decorators should be extracted.
|
||||
* @returns All information on the decorators of the class.
|
||||
*/
|
||||
protected getMemberDecoratorsFromHelperCalls(classSymbol: ClassSymbol): Map<string, Decorator[]> {
|
||||
const memberDecoratorMap = new Map<string, Decorator[]>();
|
||||
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
|
||||
helperCalls.forEach(helperCall => {
|
||||
const {memberDecorators} = this.reflectDecoratorsFromHelperCall(
|
||||
helperCall, makeMemberTargetFilter(classSymbol.name));
|
||||
memberDecorators.forEach((decorators, memberName) => {
|
||||
if (memberName) {
|
||||
const memberDecorators =
|
||||
memberDecoratorMap.has(memberName) ? memberDecoratorMap.get(memberName) ! : [];
|
||||
const coreDecorators = decorators.filter(decorator => this.isFromCore(decorator));
|
||||
memberDecoratorMap.set(memberName, memberDecorators.concat(coreDecorators));
|
||||
}
|
||||
});
|
||||
});
|
||||
return memberDecoratorMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract decorator info from `__decorate` helper function calls.
|
||||
* @param helperCall the call to a helper that may contain decorator calls
|
||||
* @param targetFilter a function to filter out targets that we are not interested in.
|
||||
* @returns a mapping from member name to decorators, where the key is either the name of the
|
||||
* member or `undefined` if it refers to decorators on the class as a whole.
|
||||
*/
|
||||
protected reflectDecoratorsFromHelperCall(
|
||||
helperCall: ts.CallExpression, targetFilter: TargetFilter):
|
||||
{classDecorators: Decorator[], memberDecorators: Map<string, Decorator[]>} {
|
||||
const classDecorators: Decorator[] = [];
|
||||
protected computeDecoratorInfoFromHelperCalls(classSymbol: ClassSymbol): DecoratorInfo {
|
||||
let classDecorators: Decorator[]|null = null;
|
||||
const memberDecorators = new Map<string, Decorator[]>();
|
||||
const constructorParamInfo: ParamInfo[] = [];
|
||||
|
||||
// First check that the `target` argument is correct
|
||||
if (targetFilter(helperCall.arguments[1])) {
|
||||
// Grab the `decorators` argument which should be an array of calls
|
||||
const decoratorCalls = helperCall.arguments[0];
|
||||
if (decoratorCalls && ts.isArrayLiteralExpression(decoratorCalls)) {
|
||||
decoratorCalls.elements.forEach(element => {
|
||||
// We only care about those elements that are actual calls
|
||||
if (ts.isCallExpression(element)) {
|
||||
const decorator = this.reflectDecoratorCall(element);
|
||||
if (decorator) {
|
||||
const keyArg = helperCall.arguments[2];
|
||||
const keyName = keyArg && ts.isStringLiteral(keyArg) ? keyArg.text : undefined;
|
||||
if (keyName === undefined) {
|
||||
classDecorators.push(decorator);
|
||||
} else {
|
||||
const decorators =
|
||||
memberDecorators.has(keyName) ? memberDecorators.get(keyName) ! : [];
|
||||
decorators.push(decorator);
|
||||
memberDecorators.set(keyName, decorators);
|
||||
}
|
||||
}
|
||||
const getConstructorParamInfo = (index: number) => {
|
||||
let param = constructorParamInfo[index];
|
||||
if (param === undefined) {
|
||||
param = constructorParamInfo[index] = {decorators: null, typeExpression: null};
|
||||
}
|
||||
return param;
|
||||
};
|
||||
|
||||
// All relevant information can be extracted from calls to `__decorate`, obtain these first.
|
||||
// Note that although the helper calls are retrieved using the class symbol, the result may
|
||||
// contain helper calls corresponding with unrelated classes. Therefore, each helper call still
|
||||
// has to be checked to actually correspond with the class symbol.
|
||||
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
|
||||
|
||||
for (const helperCall of helperCalls) {
|
||||
if (isClassDecorateCall(helperCall, classSymbol.name)) {
|
||||
// This `__decorate` call is targeting the class itself.
|
||||
const helperArgs = helperCall.arguments[0];
|
||||
|
||||
for (const element of helperArgs.elements) {
|
||||
const entry = this.reflectDecorateHelperEntry(element);
|
||||
if (entry === null) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
if (entry.type === 'decorator') {
|
||||
// The helper arg was reflected to represent an actual decorator
|
||||
if (this.isFromCore(entry.decorator)) {
|
||||
(classDecorators || (classDecorators = [])).push(entry.decorator);
|
||||
}
|
||||
} else if (entry.type === 'param:decorators') {
|
||||
// The helper arg represents a decorator for a parameter. Since it's applied to the
|
||||
// class, it corresponds with a constructor parameter of the class.
|
||||
const param = getConstructorParamInfo(entry.index);
|
||||
(param.decorators || (param.decorators = [])).push(entry.decorator);
|
||||
} else if (entry.type === 'params') {
|
||||
// The helper arg represents the types of the parameters. Since it's applied to the
|
||||
// class, it corresponds with the constructor parameters of the class.
|
||||
entry.types.forEach(
|
||||
(type, index) => getConstructorParamInfo(index).typeExpression = type);
|
||||
}
|
||||
}
|
||||
} else if (isMemberDecorateCall(helperCall, classSymbol.name)) {
|
||||
// The `__decorate` call is targeting a member of the class
|
||||
const helperArgs = helperCall.arguments[0];
|
||||
const memberName = helperCall.arguments[2].text;
|
||||
|
||||
for (const element of helperArgs.elements) {
|
||||
const entry = this.reflectDecorateHelperEntry(element);
|
||||
if (entry === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.type === 'decorator') {
|
||||
// The helper arg was reflected to represent an actual decorator.
|
||||
if (this.isFromCore(entry.decorator)) {
|
||||
const decorators =
|
||||
memberDecorators.has(memberName) ? memberDecorators.get(memberName) ! : [];
|
||||
decorators.push(entry.decorator);
|
||||
memberDecorators.set(memberName, decorators);
|
||||
}
|
||||
} else {
|
||||
// Information on decorated parameters is not interesting for ngcc, so it's ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {classDecorators, memberDecorators};
|
||||
|
||||
return {classDecorators, memberDecorators, constructorParamInfo};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the decorator information from a call to a decorator as a function.
|
||||
* This happens when the decorators has been used in a `__decorate` helper call.
|
||||
* For example:
|
||||
* Extract the details of an entry within a `__decorate` helper call. For example, given the
|
||||
* following code:
|
||||
*
|
||||
* ```
|
||||
* __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* tslib_1.__param(2, Inject(INJECTED_TOKEN)),
|
||||
* tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* Here the `Directive` decorator is decorating `SomeDirective` and the options for
|
||||
* the decorator are passed as arguments to the `Directive()` call.
|
||||
* it can be seen that there are calls to regular decorators (the `Directive`) and calls into
|
||||
* `tslib` functions which have been inserted by TypeScript. Therefore, this function classifies
|
||||
* a call to correspond with
|
||||
* 1. a real decorator like `Directive` above, or
|
||||
* 2. a decorated parameter, corresponding with `__param` calls from `tslib`, or
|
||||
* 3. the type information of parameters, corresponding with `__metadata` call from `tslib`
|
||||
*
|
||||
* @param call the call to the decorator.
|
||||
* @returns a decorator containing the reflected information, or null if the call
|
||||
* is not a valid decorator call.
|
||||
* @param expression the expression that needs to be reflected into a `DecorateHelperEntry`
|
||||
* @returns an object that indicates which of the three categories the call represents, together
|
||||
* with the reflected information of the call, or null if the call is not a valid decorate call.
|
||||
*/
|
||||
protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null {
|
||||
const decoratorExpression = call.expression;
|
||||
if (isDecoratorIdentifier(decoratorExpression)) {
|
||||
// We found a decorator!
|
||||
const decoratorIdentifier =
|
||||
ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name;
|
||||
protected reflectDecorateHelperEntry(expression: ts.Expression): DecorateHelperEntry|null {
|
||||
// We only care about those elements that are actual calls
|
||||
if (!ts.isCallExpression(expression)) {
|
||||
return null;
|
||||
}
|
||||
const call = expression;
|
||||
|
||||
const helperName = getCalleeName(call);
|
||||
if (helperName === '__metadata') {
|
||||
// This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object
|
||||
// if the metadata key is "design:paramtypes".
|
||||
const key = call.arguments[0];
|
||||
if (key === undefined || !ts.isStringLiteral(key) || key.text !== 'design:paramtypes') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = call.arguments[1];
|
||||
if (value === undefined || !ts.isArrayLiteralExpression(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name: decoratorIdentifier.text,
|
||||
identifier: decoratorIdentifier,
|
||||
import: this.getImportOfIdentifier(decoratorIdentifier),
|
||||
node: call,
|
||||
args: Array.from(call.arguments)
|
||||
type: 'params',
|
||||
types: Array.from(value.elements),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
if (helperName === '__param') {
|
||||
// This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object.
|
||||
const indexArg = call.arguments[0];
|
||||
const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN;
|
||||
if (isNaN(index)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decoratorCall = call.arguments[1];
|
||||
if (decoratorCall === undefined || !ts.isCallExpression(decoratorCall)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decorator = this.reflectDecoratorCall(decoratorCall);
|
||||
if (decorator === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'param:decorators',
|
||||
index,
|
||||
decorator,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise attempt to reflect it as a regular decorator.
|
||||
const decorator = this.reflectDecoratorCall(call);
|
||||
if (decorator === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'decorator',
|
||||
decorator,
|
||||
};
|
||||
}
|
||||
|
||||
protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null {
|
||||
const decoratorExpression = call.expression;
|
||||
if (!isDecoratorIdentifier(decoratorExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We found a decorator!
|
||||
const decoratorIdentifier =
|
||||
ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name;
|
||||
|
||||
return {
|
||||
name: decoratorIdentifier.text,
|
||||
identifier: decoratorExpression,
|
||||
import: this.getImportOfIdentifier(decoratorIdentifier),
|
||||
node: call,
|
||||
args: Array.from(call.arguments),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1070,14 +1203,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
*/
|
||||
protected getConstructorParamInfo(
|
||||
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): CtorParameter[] {
|
||||
const paramsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS);
|
||||
const paramInfo: ParamInfo[]|null = paramsProperty ?
|
||||
this.getParamInfoFromStaticProperty(paramsProperty) :
|
||||
this.getParamInfoFromHelperCall(classSymbol, parameterNodes);
|
||||
const {constructorParamInfo} = this.acquireDecoratorInfo(classSymbol);
|
||||
|
||||
return parameterNodes.map((node, index) => {
|
||||
const {decorators, typeExpression} = paramInfo && paramInfo[index] ?
|
||||
paramInfo[index] :
|
||||
const {decorators, typeExpression} = constructorParamInfo[index] ?
|
||||
constructorParamInfo[index] :
|
||||
{decorators: null, typeExpression: null};
|
||||
const nameNode = node.name;
|
||||
return {
|
||||
@ -1153,58 +1283,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameter type and decorators for a class where the information is stored via
|
||||
* calls to `__decorate` helpers.
|
||||
*
|
||||
* Reflect over the helpers to find the decorators and types about each of
|
||||
* the class's constructor parameters.
|
||||
*
|
||||
* @param classSymbol the class whose parameter info we want to get.
|
||||
* @param parameterNodes the array of TypeScript parameter nodes for this class's constructor.
|
||||
* @returns an array of objects containing the type and decorators for each parameter.
|
||||
*/
|
||||
protected getParamInfoFromHelperCall(
|
||||
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): ParamInfo[] {
|
||||
const parameters: ParamInfo[] =
|
||||
parameterNodes.map(() => ({typeExpression: null, decorators: null}));
|
||||
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
|
||||
helperCalls.forEach(helperCall => {
|
||||
const {classDecorators} =
|
||||
this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(classSymbol.name));
|
||||
classDecorators.forEach(call => {
|
||||
switch (call.name) {
|
||||
case '__metadata':
|
||||
const metadataArg = call.args && call.args[0];
|
||||
const typesArg = call.args && call.args[1];
|
||||
const isParamTypeDecorator = metadataArg && ts.isStringLiteral(metadataArg) &&
|
||||
metadataArg.text === 'design:paramtypes';
|
||||
const types = typesArg && ts.isArrayLiteralExpression(typesArg) && typesArg.elements;
|
||||
if (isParamTypeDecorator && types) {
|
||||
types.forEach((type, index) => parameters[index].typeExpression = type);
|
||||
}
|
||||
break;
|
||||
case '__param':
|
||||
const paramIndexArg = call.args && call.args[0];
|
||||
const decoratorCallArg = call.args && call.args[1];
|
||||
const paramIndex = paramIndexArg && ts.isNumericLiteral(paramIndexArg) ?
|
||||
parseInt(paramIndexArg.text, 10) :
|
||||
NaN;
|
||||
const decorator = decoratorCallArg && ts.isCallExpression(decoratorCallArg) ?
|
||||
this.reflectDecoratorCall(decoratorCallArg) :
|
||||
null;
|
||||
if (!isNaN(paramIndex) && decorator) {
|
||||
const decorators = parameters[paramIndex].decorators =
|
||||
parameters[paramIndex].decorators || [];
|
||||
decorators.push(decorator);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search statements related to the given class for calls to the specified helper.
|
||||
* @param classSymbol the class whose helper calls we are interested in.
|
||||
@ -1377,6 +1455,72 @@ export type ParamInfo = {
|
||||
typeExpression: ts.Expression | null
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a call to `tslib.__metadata` as present in `tslib.__decorate` calls. This is a
|
||||
* synthetic decorator inserted by TypeScript that contains reflection information about the
|
||||
* target of the decorator, i.e. the class or property.
|
||||
*/
|
||||
export interface ParameterTypes {
|
||||
type: 'params';
|
||||
types: ts.Expression[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to `tslib.__param` as present in `tslib.__decorate` calls. This contains
|
||||
* information on any decorators were applied to a certain parameter.
|
||||
*/
|
||||
export interface ParameterDecorators {
|
||||
type: 'param:decorators';
|
||||
index: number;
|
||||
decorator: Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to a decorator as it was present in the original source code, as present in
|
||||
* `tslib.__decorate` calls.
|
||||
*/
|
||||
export interface DecoratorCall {
|
||||
type: 'decorator';
|
||||
decorator: Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the different kinds of decorate helpers that may be present as first argument to
|
||||
* `tslib.__decorate`, as follows:
|
||||
*
|
||||
* ```
|
||||
* __decorate([
|
||||
* Directive({ selector: '[someDirective]' }),
|
||||
* tslib_1.__param(2, Inject(INJECTED_TOKEN)),
|
||||
* tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
|
||||
* ], SomeDirective);
|
||||
* ```
|
||||
*/
|
||||
export type DecorateHelperEntry = ParameterTypes | ParameterDecorators | DecoratorCall;
|
||||
|
||||
/**
|
||||
* The recorded decorator information of a single class. This information is cached in the host.
|
||||
*/
|
||||
interface DecoratorInfo {
|
||||
/**
|
||||
* All decorators that were present on the class. If no decorators were present, this is `null`
|
||||
*/
|
||||
classDecorators: Decorator[]|null;
|
||||
|
||||
/**
|
||||
* All decorators per member of the class they were present on.
|
||||
*/
|
||||
memberDecorators: Map<string, Decorator[]>;
|
||||
|
||||
/**
|
||||
* Represents the constructor parameter information, such as the type of a parameter and all
|
||||
* decorators for a certain parameter. Indices in this array correspond with the parameter's index
|
||||
* in the constructor. Note that this array may be sparse, i.e. certain constructor parameters may
|
||||
* not have any info recorded.
|
||||
*/
|
||||
constructorParamInfo: ParamInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A statement node that represents an assignment.
|
||||
*/
|
||||
@ -1397,27 +1541,55 @@ export function isAssignment(node: ts.Node): node is ts.AssignmentExpression<ts.
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of a function that can be used to filter out helpers based on their target.
|
||||
* This is used in `reflectDecoratorsFromHelperCall()`.
|
||||
* Tests whether the provided call expression targets a class, by verifying its arguments are
|
||||
* according to the following form:
|
||||
*
|
||||
* ```
|
||||
* __decorate([], SomeDirective);
|
||||
* ```
|
||||
*
|
||||
* @param call the call expression that is tested to represent a class decorator call.
|
||||
* @param className the name of the class that the call needs to correspond with.
|
||||
*/
|
||||
export type TargetFilter = (target: ts.Expression) => boolean;
|
||||
export function isClassDecorateCall(call: ts.CallExpression, className: string):
|
||||
call is ts.CallExpression&{arguments: [ts.ArrayLiteralExpression, ts.Expression]} {
|
||||
const helperArgs = call.arguments[0];
|
||||
if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that tests whether the given expression is a class target.
|
||||
* @param className the name of the class we want to target.
|
||||
*/
|
||||
export function makeClassTargetFilter(className: string): TargetFilter {
|
||||
return (target: ts.Expression): boolean => ts.isIdentifier(target) && target.text === className;
|
||||
const target = call.arguments[1];
|
||||
return target !== undefined && ts.isIdentifier(target) && target.text === className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that tests whether the given expression is a class member target.
|
||||
* @param className the name of the class we want to target.
|
||||
* Tests whether the provided call expression targets a member of the class, by verifying its
|
||||
* arguments are according to the following form:
|
||||
*
|
||||
* ```
|
||||
* __decorate([], SomeDirective.prototype, "member", void 0);
|
||||
* ```
|
||||
*
|
||||
* @param call the call expression that is tested to represent a member decorator call.
|
||||
* @param className the name of the class that the call needs to correspond with.
|
||||
*/
|
||||
export function makeMemberTargetFilter(className: string): TargetFilter {
|
||||
return (target: ts.Expression): boolean => ts.isPropertyAccessExpression(target) &&
|
||||
ts.isIdentifier(target.expression) && target.expression.text === className &&
|
||||
target.name.text === 'prototype';
|
||||
export function isMemberDecorateCall(call: ts.CallExpression, className: string):
|
||||
call is ts.CallExpression&
|
||||
{arguments: [ts.ArrayLiteralExpression, ts.StringLiteral, ts.StringLiteral]} {
|
||||
const helperArgs = call.arguments[0];
|
||||
if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = call.arguments[1];
|
||||
if (target === undefined || !ts.isPropertyAccessExpression(target) ||
|
||||
!ts.isIdentifier(target.expression) || target.expression.text !== className ||
|
||||
target.name.text !== 'prototype') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const memberName = call.arguments[2];
|
||||
return memberName !== undefined && ts.isStringLiteral(memberName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1435,10 +1607,10 @@ export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression
|
||||
*/
|
||||
function getCalleeName(call: ts.CallExpression): string|null {
|
||||
if (ts.isIdentifier(call.expression)) {
|
||||
return call.expression.text;
|
||||
return stripDollarSuffix(call.expression.text);
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(call.expression)) {
|
||||
return call.expression.name.text;
|
||||
return stripDollarSuffix(call.expression.name.text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
|
||||
|
||||
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
|
||||
import {DependencyResolver, InvalidEntryPoint, SortedEntryPointsInfo} from './dependencies/dependency_resolver';
|
||||
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
|
||||
@ -13,11 +14,12 @@ import {ModuleResolver} from './dependencies/module_resolver';
|
||||
import {UmdDependencyHost} from './dependencies/umd_dependency_host';
|
||||
import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder';
|
||||
import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder';
|
||||
import {AnalyzeFn, CreateCompileFn, EntryPointProcessingMetadata, ExecuteFn, Task, TaskProcessingOutcome} from './execution/api';
|
||||
import {ConsoleLogger, LogLevel} from './logging/console_logger';
|
||||
import {Logger} from './logging/logger';
|
||||
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
|
||||
import {NgccConfiguration} from './packages/configuration';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
|
||||
import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
|
||||
import {makeEntryPointBundle} from './packages/entry_point_bundle';
|
||||
import {Transformer} from './packages/transformer';
|
||||
import {PathMappings} from './utils';
|
||||
@ -25,6 +27,7 @@ import {FileWriter} from './writing/file_writer';
|
||||
import {InPlaceFileWriter} from './writing/in_place_file_writer';
|
||||
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
|
||||
|
||||
|
||||
/**
|
||||
* The options to configure the ngcc compiler.
|
||||
*/
|
||||
@ -68,8 +71,6 @@ export interface NgccOptions {
|
||||
fileSystem?: FileSystem;
|
||||
}
|
||||
|
||||
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'umd', 'commonjs'];
|
||||
|
||||
/**
|
||||
* This is the main entry-point into ngcc (aNGular Compatibility Compiler).
|
||||
*
|
||||
@ -83,82 +84,171 @@ export function mainNgcc(
|
||||
compileAllFormats = true, createNewEntryPointFormats = false,
|
||||
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
|
||||
const fileSystem = getFileSystem();
|
||||
const transformer = new Transformer(fileSystem, logger);
|
||||
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
|
||||
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
|
||||
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
|
||||
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
|
||||
const resolver = new DependencyResolver(fileSystem, logger, {
|
||||
esm5: esmDependencyHost,
|
||||
esm2015: esmDependencyHost,
|
||||
umd: umdDependencyHost,
|
||||
commonjs: commonJsDependencyHost
|
||||
});
|
||||
const absBasePath = absoluteFrom(basePath);
|
||||
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
|
||||
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
|
||||
const entryPoints = getEntryPoints(
|
||||
fileSystem, config, logger, resolver, absBasePath, targetEntryPointPath, pathMappings,
|
||||
propertiesToConsider, compileAllFormats);
|
||||
for (const entryPoint of entryPoints) {
|
||||
// Are we compiling the Angular core?
|
||||
const isCore = entryPoint.name === '@angular/core';
|
||||
|
||||
const compiledFormats = new Set<string>();
|
||||
const entryPointPackageJson = entryPoint.packageJson;
|
||||
const entryPointPackageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
|
||||
// The function for performing the analysis.
|
||||
const analyzeFn: AnalyzeFn = () => {
|
||||
const supportedPropertiesToConsider = ensureSupportedProperties(propertiesToConsider);
|
||||
|
||||
const hasProcessedDts = hasBeenProcessed(entryPointPackageJson, 'typings');
|
||||
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
|
||||
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
|
||||
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
|
||||
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
|
||||
const dependencyResolver = new DependencyResolver(fileSystem, logger, {
|
||||
esm5: esmDependencyHost,
|
||||
esm2015: esmDependencyHost,
|
||||
umd: umdDependencyHost,
|
||||
commonjs: commonJsDependencyHost
|
||||
});
|
||||
|
||||
for (let i = 0; i < propertiesToConsider.length; i++) {
|
||||
const property = propertiesToConsider[i] as EntryPointJsonProperty;
|
||||
const formatPath = entryPointPackageJson[property];
|
||||
const format = getEntryPointFormat(fileSystem, entryPoint, property);
|
||||
const absBasePath = absoluteFrom(basePath);
|
||||
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
|
||||
const entryPoints = getEntryPoints(
|
||||
fileSystem, config, logger, dependencyResolver, absBasePath, targetEntryPointPath,
|
||||
pathMappings, supportedPropertiesToConsider, compileAllFormats);
|
||||
|
||||
// No format then this property is not supposed to be compiled.
|
||||
if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
|
||||
const processingMetadataPerEntryPoint = new Map<string, EntryPointProcessingMetadata>();
|
||||
const tasks: Task[] = [];
|
||||
|
||||
if (hasBeenProcessed(entryPointPackageJson, property)) {
|
||||
compiledFormats.add(formatPath);
|
||||
logger.debug(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
|
||||
continue;
|
||||
for (const entryPoint of entryPoints) {
|
||||
const packageJson = entryPoint.packageJson;
|
||||
const hasProcessedTypings = hasBeenProcessed(packageJson, 'typings');
|
||||
const {propertiesToProcess, propertyToPropertiesToMarkAsProcessed} =
|
||||
getPropertiesToProcessAndMarkAsProcessed(packageJson, supportedPropertiesToConsider);
|
||||
let processDts = !hasProcessedTypings;
|
||||
|
||||
for (const formatProperty of propertiesToProcess) {
|
||||
const formatPropertiesToMarkAsProcessed =
|
||||
propertyToPropertiesToMarkAsProcessed.get(formatProperty) !;
|
||||
tasks.push({entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts});
|
||||
|
||||
// Only process typings for the first property (if not already processed).
|
||||
processDts = false;
|
||||
}
|
||||
|
||||
const isFirstFormat = compiledFormats.size === 0;
|
||||
const processDts = !hasProcessedDts && isFirstFormat;
|
||||
|
||||
// We don't break if this if statement fails because we still want to mark
|
||||
// the property as processed even if its underlying format has been built already.
|
||||
if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) {
|
||||
const bundle = makeEntryPointBundle(
|
||||
fileSystem, entryPoint, formatPath, isCore, property, format, processDts, pathMappings,
|
||||
true);
|
||||
if (bundle) {
|
||||
logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`);
|
||||
const transformedFiles = transformer.transform(bundle);
|
||||
fileWriter.writeBundle(entryPoint, bundle, transformedFiles);
|
||||
compiledFormats.add(formatPath);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`);
|
||||
}
|
||||
}
|
||||
|
||||
// Either this format was just compiled or its underlying format was compiled because of a
|
||||
// previous property.
|
||||
if (compiledFormats.has(formatPath)) {
|
||||
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, property);
|
||||
if (processDts) {
|
||||
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, 'typings');
|
||||
}
|
||||
}
|
||||
processingMetadataPerEntryPoint.set(entryPoint.path, {
|
||||
hasProcessedTypings,
|
||||
hasAnyProcessedFormat: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (compiledFormats.size === 0) {
|
||||
return {processingMetadataPerEntryPoint, tasks};
|
||||
};
|
||||
|
||||
// The function for creating the `compile()` function.
|
||||
const createCompileFn: CreateCompileFn = onTaskCompleted => {
|
||||
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
|
||||
const transformer = new Transformer(fileSystem, logger);
|
||||
|
||||
return (task: Task) => {
|
||||
const {entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts} = task;
|
||||
|
||||
const isCore = entryPoint.name === '@angular/core'; // Are we compiling the Angular core?
|
||||
const packageJson = entryPoint.packageJson;
|
||||
const formatPath = packageJson[formatProperty];
|
||||
const format = getEntryPointFormat(fileSystem, entryPoint, formatProperty);
|
||||
|
||||
// All properties listed in `propertiesToProcess` are guaranteed to point to a format-path
|
||||
// (i.e. they exist in `entryPointPackageJson`). Furthermore, they are also guaranteed to be
|
||||
// among `SUPPORTED_FORMAT_PROPERTIES`.
|
||||
// Based on the above, `formatPath` should always be defined and `getEntryPointFormat()`
|
||||
// should always return a format here (and not `undefined`).
|
||||
if (!formatPath || !format) {
|
||||
// This should never happen.
|
||||
throw new Error(
|
||||
`Invariant violated: No format-path or format for ${entryPoint.path} : ` +
|
||||
`${formatProperty} (formatPath: ${formatPath} | format: ${format})`);
|
||||
}
|
||||
|
||||
// The format-path which the property maps to is already processed - nothing to do.
|
||||
if (hasBeenProcessed(packageJson, formatProperty)) {
|
||||
logger.debug(`Skipping ${entryPoint.name} : ${formatProperty} (already compiled).`);
|
||||
onTaskCompleted(task, TaskProcessingOutcome.AlreadyProcessed);
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = makeEntryPointBundle(
|
||||
fileSystem, entryPoint, formatPath, isCore, format, processDts, pathMappings, true);
|
||||
|
||||
logger.info(`Compiling ${entryPoint.name} : ${formatProperty} as ${format}`);
|
||||
|
||||
const transformedFiles = transformer.transform(bundle);
|
||||
fileWriter.writeBundle(bundle, transformedFiles, formatPropertiesToMarkAsProcessed);
|
||||
|
||||
onTaskCompleted(task, TaskProcessingOutcome.Processed);
|
||||
};
|
||||
};
|
||||
|
||||
// The function for actually planning and getting the work done.
|
||||
const executeFn: ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => {
|
||||
const {processingMetadataPerEntryPoint, tasks} = analyzeFn();
|
||||
const compile = createCompileFn((task, outcome) => {
|
||||
const {entryPoint, formatPropertiesToMarkAsProcessed, processDts} = task;
|
||||
const processingMeta = processingMetadataPerEntryPoint.get(entryPoint.path) !;
|
||||
processingMeta.hasAnyProcessedFormat = true;
|
||||
|
||||
if (outcome === TaskProcessingOutcome.Processed) {
|
||||
const packageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
|
||||
const propsToMarkAsProcessed: (EntryPointJsonProperty | 'typings')[] =
|
||||
[...formatPropertiesToMarkAsProcessed];
|
||||
|
||||
if (processDts) {
|
||||
processingMeta.hasProcessedTypings = true;
|
||||
propsToMarkAsProcessed.push('typings');
|
||||
}
|
||||
|
||||
markAsProcessed(
|
||||
fileSystem, entryPoint.packageJson, packageJsonPath, propsToMarkAsProcessed);
|
||||
}
|
||||
});
|
||||
|
||||
// Process all tasks.
|
||||
for (const task of tasks) {
|
||||
const processingMeta = processingMetadataPerEntryPoint.get(task.entryPoint.path) !;
|
||||
|
||||
// If we only need one format processed and we already have one for the corresponding
|
||||
// entry-point, skip the task.
|
||||
if (!compileAllFormats && processingMeta.hasAnyProcessedFormat) continue;
|
||||
|
||||
compile(task);
|
||||
}
|
||||
|
||||
// Check for entry-points for which we could not process any format at all.
|
||||
const unprocessedEntryPointPaths =
|
||||
Array.from(processingMetadataPerEntryPoint.entries())
|
||||
.filter(([, processingMeta]) => !processingMeta.hasAnyProcessedFormat)
|
||||
.map(([entryPointPath]) => `\n - ${entryPointPath}`)
|
||||
.join('');
|
||||
|
||||
if (unprocessedEntryPointPaths) {
|
||||
throw new Error(
|
||||
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToConsider}.`);
|
||||
'Failed to compile any formats for the following entry-points (tried ' +
|
||||
`${propertiesToConsider.join(', ')}): ${unprocessedEntryPointPaths}`);
|
||||
}
|
||||
};
|
||||
|
||||
return executeFn(analyzeFn, createCompileFn);
|
||||
}
|
||||
|
||||
function ensureSupportedProperties(properties: string[]): EntryPointJsonProperty[] {
|
||||
// Short-circuit the case where `properties` has fallen back to the default value:
|
||||
// `SUPPORTED_FORMAT_PROPERTIES`
|
||||
if (properties === SUPPORTED_FORMAT_PROPERTIES) return SUPPORTED_FORMAT_PROPERTIES;
|
||||
|
||||
const supportedProperties: EntryPointJsonProperty[] = [];
|
||||
|
||||
for (const prop of properties as EntryPointJsonProperty[]) {
|
||||
if (SUPPORTED_FORMAT_PROPERTIES.indexOf(prop) !== -1) {
|
||||
supportedProperties.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
if (supportedProperties.length === 0) {
|
||||
throw new Error(
|
||||
`No supported format property to consider among [${properties.join(', ')}]. ` +
|
||||
`Supported properties: ${SUPPORTED_FORMAT_PROPERTIES.join(', ')}`);
|
||||
}
|
||||
|
||||
return supportedProperties;
|
||||
}
|
||||
|
||||
function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
|
||||
@ -192,8 +282,15 @@ function getTargetedEntryPoints(
|
||||
const finder = new TargetedEntryPointFinder(
|
||||
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
|
||||
const entryPointInfo = finder.findEntryPoints();
|
||||
const invalidTarget = entryPointInfo.invalidEntryPoints.find(
|
||||
i => i.entryPoint.path === absoluteTargetEntryPointPath);
|
||||
if (invalidTarget !== undefined) {
|
||||
throw new Error(
|
||||
`The target entry-point "${invalidTarget.entryPoint.name}" has missing dependencies:\n` +
|
||||
invalidTarget.missingDependencies.map(dep => ` - ${dep}\n`));
|
||||
}
|
||||
if (entryPointInfo.entryPoints.length === 0) {
|
||||
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
|
||||
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath);
|
||||
}
|
||||
return entryPointInfo;
|
||||
}
|
||||
@ -242,14 +339,13 @@ function hasProcessedTargetEntryPoint(
|
||||
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid
|
||||
* triggering ngcc for this entry-point in the future.
|
||||
*/
|
||||
function markNonAngularPackageAsProcessed(
|
||||
fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) {
|
||||
function markNonAngularPackageAsProcessed(fs: FileSystem, path: AbsoluteFsPath) {
|
||||
const packageJsonPath = resolve(path, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
|
||||
propertiesToConsider.forEach(formatProperty => {
|
||||
if (packageJson[formatProperty])
|
||||
markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
|
||||
});
|
||||
|
||||
// Note: We are marking all supported properties as processed, even if they don't exist in the
|
||||
// `package.json` file. While this is redundant, it is also harmless.
|
||||
markAsProcessed(fs, packageJson, packageJsonPath, SUPPORTED_FORMAT_PROPERTIES);
|
||||
}
|
||||
|
||||
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
|
||||
@ -260,3 +356,60 @@ function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryP
|
||||
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function computes and returns the following:
|
||||
* - `propertiesToProcess`: An (ordered) list of properties that exist and need to be processed,
|
||||
* based on the specified `propertiesToConsider`, the properties in `package.json` and their
|
||||
* corresponding format-paths. NOTE: Only one property per format-path needs to be processed.
|
||||
* - `propertyToPropertiesToMarkAsProcessed`: A mapping from each property in `propertiesToProcess`
|
||||
* to the list of other properties in `package.json` that need to be marked as processed as soon
|
||||
* as of the former being processed.
|
||||
*/
|
||||
function getPropertiesToProcessAndMarkAsProcessed(
|
||||
packageJson: EntryPointPackageJson, propertiesToConsider: EntryPointJsonProperty[]): {
|
||||
propertiesToProcess: EntryPointJsonProperty[];
|
||||
propertyToPropertiesToMarkAsProcessed: Map<EntryPointJsonProperty, EntryPointJsonProperty[]>;
|
||||
} {
|
||||
const formatPathsToConsider = new Set<string>();
|
||||
|
||||
const propertiesToProcess: EntryPointJsonProperty[] = [];
|
||||
for (const prop of propertiesToConsider) {
|
||||
// Ignore properties that are not in `package.json`.
|
||||
if (!packageJson.hasOwnProperty(prop)) continue;
|
||||
|
||||
const formatPath = packageJson[prop] !;
|
||||
|
||||
// Ignore properties that map to the same format-path as a preceding property.
|
||||
if (formatPathsToConsider.has(formatPath)) continue;
|
||||
|
||||
// Process this property, because it is the first one to map to this format-path.
|
||||
formatPathsToConsider.add(formatPath);
|
||||
propertiesToProcess.push(prop);
|
||||
}
|
||||
|
||||
const formatPathToProperties: {[formatPath: string]: EntryPointJsonProperty[]} = {};
|
||||
for (const prop of SUPPORTED_FORMAT_PROPERTIES) {
|
||||
// Ignore properties that are not in `package.json`.
|
||||
if (!packageJson.hasOwnProperty(prop)) continue;
|
||||
|
||||
const formatPath = packageJson[prop] !;
|
||||
|
||||
// Ignore properties that do not map to a format-path that will be considered.
|
||||
if (!formatPathsToConsider.has(formatPath)) continue;
|
||||
|
||||
// Add this property to the map.
|
||||
const list = formatPathToProperties[formatPath] || (formatPathToProperties[formatPath] = []);
|
||||
list.push(prop);
|
||||
}
|
||||
|
||||
const propertyToPropertiesToMarkAsProcessed =
|
||||
new Map<EntryPointJsonProperty, EntryPointJsonProperty[]>();
|
||||
for (const prop of propertiesToConsider) {
|
||||
const formatPath = packageJson[prop] !;
|
||||
const propertiesToMarkAsProcessed = formatPathToProperties[formatPath];
|
||||
propertyToPropertiesToMarkAsProcessed.set(prop, propertiesToMarkAsProcessed);
|
||||
}
|
||||
|
||||
return {propertiesToProcess, propertyToPropertiesToMarkAsProcessed};
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
|
||||
* @throws Error if the entry-point has already been processed with a different ngcc version.
|
||||
*/
|
||||
export function hasBeenProcessed(
|
||||
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty): boolean {
|
||||
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty | 'typings'): boolean {
|
||||
if (!packageJson.__processed_by_ivy_ngcc__) {
|
||||
return false;
|
||||
}
|
||||
@ -38,17 +38,36 @@ export function hasBeenProcessed(
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a build marker for the given entry-point and format property, to indicate that it has
|
||||
* Write a build marker for the given entry-point and format properties, to indicate that they have
|
||||
* been compiled by this version of ngcc.
|
||||
*
|
||||
* @param entryPoint the entry-point to write a marker.
|
||||
* @param format the property in the package.json of the format for which we are writing the marker.
|
||||
* @param fs The current file-system being used.
|
||||
* @param packageJson The parsed contents of the `package.json` file for the entry-point.
|
||||
* @param packageJsonPath The absolute path to the `package.json` file.
|
||||
* @param properties The properties in the `package.json` of the formats for which we are writing
|
||||
* the marker.
|
||||
*/
|
||||
export function markAsProcessed(
|
||||
fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
|
||||
format: EntryPointJsonProperty) {
|
||||
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
|
||||
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
|
||||
properties: (EntryPointJsonProperty | 'typings')[]) {
|
||||
const processed =
|
||||
packageJson.__processed_by_ivy_ngcc__ || (packageJson.__processed_by_ivy_ngcc__ = {});
|
||||
|
||||
for (const prop of properties) {
|
||||
processed[prop] = NGCC_VERSION;
|
||||
}
|
||||
|
||||
const scripts = packageJson.scripts || (packageJson.scripts = {});
|
||||
scripts.prepublishOnly__ivy_ngcc_bak =
|
||||
scripts.prepublishOnly__ivy_ngcc_bak || scripts.prepublishOnly;
|
||||
|
||||
scripts.prepublishOnly = 'node --eval \"console.error(\'' +
|
||||
'ERROR: Trying to publish a package that has been compiled by NGCC. This is not allowed.\\n' +
|
||||
'Please delete and rebuild the package, without compiling with NGCC, before attempting to publish.\\n' +
|
||||
'Note that NGCC may have been run by importing this package into another project that is being built with Ivy enabled.\\n' +
|
||||
'\')\" ' +
|
||||
'&& exit 1';
|
||||
|
||||
// Just in case this package.json was synthesized due to a custom configuration
|
||||
// we will ensure that the path to the containing folder exists before we write the file.
|
||||
fs.ensureDir(dirname(packageJsonPath));
|
||||
|
@ -55,10 +55,11 @@ export interface PackageJsonFormatProperties {
|
||||
*/
|
||||
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
|
||||
name: string;
|
||||
__processed_by_ivy_ngcc__?: {[key: string]: string};
|
||||
scripts?: Record<string, string>;
|
||||
__processed_by_ivy_ngcc__?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
|
||||
export type EntryPointJsonProperty = Exclude<keyof PackageJsonFormatProperties, 'types'|'typings'>;
|
||||
// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync.
|
||||
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
|
||||
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
|
||||
@ -122,7 +123,8 @@ export function getEntryPointInfo(
|
||||
* @returns An entry-point format or `undefined` if none match the given property.
|
||||
*/
|
||||
export function getEntryPointFormat(
|
||||
fs: FileSystem, entryPoint: EntryPoint, property: string): EntryPointFormat|undefined {
|
||||
fs: FileSystem, entryPoint: EntryPoint, property: EntryPointJsonProperty): EntryPointFormat|
|
||||
undefined {
|
||||
switch (property) {
|
||||
case 'fesm2015':
|
||||
return 'esm2015';
|
||||
|
@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../../src/ngtsc/file_
|
||||
import {NgtscCompilerHost} from '../../../src/ngtsc/file_system/src/compiler_host';
|
||||
import {PathMappings} from '../utils';
|
||||
import {BundleProgram, makeBundleProgram} from './bundle_program';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from './entry_point';
|
||||
import {EntryPoint, EntryPointFormat} from './entry_point';
|
||||
import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
|
||||
|
||||
/**
|
||||
@ -19,7 +19,6 @@ import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
|
||||
*/
|
||||
export interface EntryPointBundle {
|
||||
entryPoint: EntryPoint;
|
||||
formatProperty: EntryPointJsonProperty;
|
||||
format: EntryPointFormat;
|
||||
isCore: boolean;
|
||||
isFlatCore: boolean;
|
||||
@ -34,7 +33,6 @@ export interface EntryPointBundle {
|
||||
* @param entryPoint The entry-point that contains the bundle.
|
||||
* @param formatPath The path to the source files for this bundle.
|
||||
* @param isCore This entry point is the Angular core package.
|
||||
* @param formatProperty The property in the package.json that holds the formatPath.
|
||||
* @param format The underlying format of the bundle.
|
||||
* @param transformDts Whether to transform the typings along with this bundle.
|
||||
* @param pathMappings An optional set of mappings to use when compiling files.
|
||||
@ -43,8 +41,8 @@ export interface EntryPointBundle {
|
||||
*/
|
||||
export function makeEntryPointBundle(
|
||||
fs: FileSystem, entryPoint: EntryPoint, formatPath: string, isCore: boolean,
|
||||
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean,
|
||||
pathMappings?: PathMappings, mirrorDtsFromSrc: boolean = false): EntryPointBundle|null {
|
||||
format: EntryPointFormat, transformDts: boolean, pathMappings?: PathMappings,
|
||||
mirrorDtsFromSrc: boolean = false): EntryPointBundle {
|
||||
// Create the TS program and necessary helpers.
|
||||
const options: ts.CompilerOptions = {
|
||||
allowJs: true,
|
||||
@ -69,7 +67,7 @@ export function makeEntryPointBundle(
|
||||
null;
|
||||
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||
|
||||
return {entryPoint, format, formatProperty, rootDirs, isCore, isFlatCore, src, dts};
|
||||
return {entryPoint, format, rootDirs, isCore, isFlatCore, src, dts};
|
||||
}
|
||||
|
||||
function computePotentialDtsFilesFromJsFiles(
|
||||
@ -88,4 +86,4 @@ function computePotentialDtsFilesFromJsFiles(
|
||||
}
|
||||
}
|
||||
return additionalFiles;
|
||||
}
|
||||
}
|
||||
|
@ -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+$/, '');
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {EntryPoint} from '../packages/entry_point';
|
||||
import {EntryPointJsonProperty} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {FileToWrite} from '../rendering/utils';
|
||||
|
||||
@ -14,6 +14,7 @@ import {FileToWrite} from '../rendering/utils';
|
||||
* Responsible for writing out the transformed files to disk.
|
||||
*/
|
||||
export interface FileWriter {
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]):
|
||||
void;
|
||||
writeBundle(
|
||||
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||
formatProperties: EntryPointJsonProperty[]): void;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {FileSystem, absoluteFrom, dirname} from '../../../src/ngtsc/file_system';
|
||||
import {EntryPoint} from '../packages/entry_point';
|
||||
import {EntryPointJsonProperty} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {FileToWrite} from '../rendering/utils';
|
||||
import {FileWriter} from './file_writer';
|
||||
@ -19,7 +19,9 @@ import {FileWriter} from './file_writer';
|
||||
export class InPlaceFileWriter implements FileWriter {
|
||||
constructor(protected fs: FileSystem) {}
|
||||
|
||||
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
|
||||
writeBundle(
|
||||
_bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||
_formatProperties?: EntryPointJsonProperty[]) {
|
||||
transformedFiles.forEach(file => this.writeFileAndBackup(file));
|
||||
}
|
||||
|
||||
|
@ -25,12 +25,15 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
|
||||
* `InPlaceFileWriter`).
|
||||
*/
|
||||
export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
|
||||
writeBundle(
|
||||
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||
formatProperties: EntryPointJsonProperty[]) {
|
||||
// The new folder is at the root of the overall package
|
||||
const entryPoint = bundle.entryPoint;
|
||||
const ngccFolder = join(entryPoint.package, NGCC_DIRECTORY);
|
||||
this.copyBundle(bundle, entryPoint.package, ngccFolder);
|
||||
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
|
||||
this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder);
|
||||
this.updatePackageJson(entryPoint, formatProperties, ngccFolder);
|
||||
}
|
||||
|
||||
protected copyBundle(
|
||||
@ -60,12 +63,18 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
}
|
||||
|
||||
protected updatePackageJson(
|
||||
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) {
|
||||
const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
|
||||
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
|
||||
const newFormatProperty = formatProperty + '_ivy_ngcc';
|
||||
(entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
|
||||
entryPoint: EntryPoint, formatProperties: EntryPointJsonProperty[],
|
||||
ngccFolder: AbsoluteFsPath) {
|
||||
const packageJson = entryPoint.packageJson;
|
||||
|
||||
for (const formatProperty of formatProperties) {
|
||||
const formatPath = join(entryPoint.path, packageJson[formatProperty] !);
|
||||
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
|
||||
const newFormatProperty = formatProperty + '_ivy_ngcc';
|
||||
(packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
|
||||
}
|
||||
|
||||
this.fs.writeFile(
|
||||
join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson));
|
||||
join(entryPoint.path, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`);
|
||||
}
|
||||
}
|
||||
|
@ -96,8 +96,7 @@ runInEachFileSystem(() => {
|
||||
loadTestFiles(testFiles);
|
||||
loadFakeCore(getFileSystem());
|
||||
const rootFiles = getRootFiles(testFiles);
|
||||
const bundle =
|
||||
makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
|
||||
program = bundle.src.program;
|
||||
|
||||
const reflectionHost =
|
||||
@ -373,4 +372,4 @@ class MockMigration implements Migration {
|
||||
this.log.push(`${this.name}:${clazz.name.text}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ runInEachFileSystem(() => {
|
||||
loadTestFiles(TEST_PROGRAM);
|
||||
loadTestFiles(TEST_DTS_PROGRAM);
|
||||
const bundle = makeTestEntryPointBundle(
|
||||
'test-package', 'esm2015', 'esm2015', false, getRootFiles(TEST_PROGRAM),
|
||||
'test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM),
|
||||
getRootFiles(TEST_DTS_PROGRAM));
|
||||
program = bundle.src.program;
|
||||
dtsProgram = bundle.dts;
|
||||
|
@ -236,8 +236,7 @@ runInEachFileSystem(() => {
|
||||
loadTestFiles(jsProgram);
|
||||
loadTestFiles(dtsProgram);
|
||||
const {src: {program}, dts} = makeTestEntryPointBundle(
|
||||
'test-package', 'esm2015', 'esm2015', false, getRootFiles(jsProgram),
|
||||
getRootFiles(dtsProgram));
|
||||
'test-package', 'esm2015', false, getRootFiles(jsProgram), getRootFiles(dtsProgram));
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);
|
||||
|
@ -72,7 +72,7 @@ runInEachFileSystem(() => {
|
||||
it('should check for switchable markers in all the files of the program', () => {
|
||||
loadTestFiles(TEST_PROGRAM);
|
||||
const bundle = makeTestEntryPointBundle(
|
||||
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
||||
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
||||
const program = bundle.src.program;
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
|
||||
@ -103,7 +103,7 @@ runInEachFileSystem(() => {
|
||||
it('should ignore files that are outside the package', () => {
|
||||
loadTestFiles(TEST_PROGRAM);
|
||||
const bundle = makeTestEntryPointBundle(
|
||||
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
||||
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
||||
const program = bundle.src.program;
|
||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
|
||||
@ -114,4 +114,4 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -179,6 +179,32 @@ runInEachFileSystem(() => {
|
||||
expect(sorted.entryPoints).toEqual([fifth]);
|
||||
});
|
||||
|
||||
it('should not process the provided target if it has missing dependencies', () => {
|
||||
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
|
||||
[_('/first/index.js')]: {resolved: [], missing: ['/missing']},
|
||||
}));
|
||||
const entryPoints = [first];
|
||||
let sorted: SortedEntryPointsInfo;
|
||||
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
|
||||
expect(sorted.entryPoints).toEqual([]);
|
||||
expect(sorted.invalidEntryPoints[0].entryPoint).toEqual(first);
|
||||
expect(sorted.invalidEntryPoints[0].missingDependencies).toEqual(['/missing']);
|
||||
});
|
||||
|
||||
it('should not consider builtin NodeJS modules as missing dependency', () => {
|
||||
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
|
||||
[_('/first/index.js')]: {resolved: [], missing: ['fs']},
|
||||
}));
|
||||
const entryPoints = [first];
|
||||
let sorted: SortedEntryPointsInfo;
|
||||
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
|
||||
expect(sorted.entryPoints).toEqual([first]);
|
||||
expect(sorted.invalidEntryPoints).toEqual([]);
|
||||
expect(sorted.ignoredDependencies).toEqual([]);
|
||||
});
|
||||
|
||||
it('should use the appropriate DependencyHost for each entry-point', () => {
|
||||
const esm5Host = new EsmDependencyHost(fs, moduleResolver);
|
||||
const esm2015Host = new EsmDependencyHost(fs, moduleResolver);
|
||||
|
@ -9,7 +9,7 @@ import * as ts from 'typescript';
|
||||
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
||||
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point';
|
||||
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
|
||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
||||
|
||||
@ -32,19 +32,13 @@ export function makeTestEntryPoint(
|
||||
* @param dtsFiles The typings files to include the bundle.
|
||||
*/
|
||||
export function makeTestEntryPointBundle(
|
||||
packageName: string, formatProperty: EntryPointJsonProperty, format: EntryPointFormat,
|
||||
isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
||||
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
||||
dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
|
||||
const entryPoint = makeTestEntryPoint(packageName);
|
||||
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
||||
const dts = dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], isCore) : null;
|
||||
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||
return {
|
||||
entryPoint,
|
||||
formatProperty,
|
||||
format,
|
||||
rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore
|
||||
};
|
||||
return {entryPoint, format, rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore};
|
||||
}
|
||||
|
||||
export function makeTestBundleProgram(
|
||||
@ -78,6 +72,22 @@ export function convertToDirectTsLibImport(filesystem: TestFile[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export function convertToInlineTsLib(filesystem: TestFile[], suffix: string = '') {
|
||||
return filesystem.map(file => {
|
||||
const contents = file.contents
|
||||
.replace(`import * as tslib_1 from 'tslib';`, `
|
||||
var __decorate${suffix} = null;
|
||||
var __metadata${suffix} = null;
|
||||
var __read${suffix} = null;
|
||||
var __values${suffix} = null;
|
||||
var __param${suffix} = null;
|
||||
var __extends${suffix} = null;
|
||||
var __assign${suffix} = null;
|
||||
`).replace(/tslib_1\.([_a-z]+)/gi, '$1' + suffix.replace('$', '$$'));
|
||||
return {...file, contents};
|
||||
});
|
||||
}
|
||||
|
||||
export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] {
|
||||
return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name));
|
||||
}
|
||||
|
@ -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`)', () => {
|
||||
|
@ -86,12 +86,26 @@ runInEachFileSystem(() => {
|
||||
// `test-package` has no Angular but is marked as processed.
|
||||
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
|
||||
es2015: '0.0.0-PLACEHOLDER',
|
||||
esm2015: '0.0.0-PLACEHOLDER',
|
||||
esm5: '0.0.0-PLACEHOLDER',
|
||||
fesm2015: '0.0.0-PLACEHOLDER',
|
||||
fesm5: '0.0.0-PLACEHOLDER',
|
||||
main: '0.0.0-PLACEHOLDER',
|
||||
module: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
|
||||
// * `core` is a dependency of `test-package`, but it is not processed, since test-package
|
||||
// was not processed.
|
||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should report an error if a dependency of the target does not exist', () => {
|
||||
expect(() => {
|
||||
mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'invalid-package'});
|
||||
})
|
||||
.toThrowError(
|
||||
'The target entry-point "invalid-package" has missing dependencies:\n - @angular/missing\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('early skipping of target entry-point', () => {
|
||||
@ -157,6 +171,23 @@ runInEachFileSystem(() => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip all processing if the first matching `propertyToConsider` is marked as processed',
|
||||
() => {
|
||||
const logger = new MockLogger();
|
||||
markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']);
|
||||
mainNgcc({
|
||||
basePath: '/node_modules',
|
||||
targetEntryPointPath: '@angular/common/http/testing',
|
||||
// Simulate a property that does not exist on the package.json and will be ignored.
|
||||
propertiesToConsider: ['missing', 'esm2015', 'esm5'],
|
||||
compileAllFormats: false, logger,
|
||||
});
|
||||
|
||||
expect(logger.logs.debug).toContain([
|
||||
'The target entry-point has already been processed'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -164,13 +195,22 @@ runInEachFileSystem(() => {
|
||||
const basePath = _('/node_modules');
|
||||
const targetPackageJsonPath = join(basePath, packagePath, 'package.json');
|
||||
const targetPackage = loadPackage(packagePath);
|
||||
markAsProcessed(fs, targetPackage, targetPackageJsonPath, 'typings');
|
||||
properties.forEach(
|
||||
property => markAsProcessed(fs, targetPackage, targetPackageJsonPath, property));
|
||||
markAsProcessed(fs, targetPackage, targetPackageJsonPath, ['typings', ...properties]);
|
||||
}
|
||||
|
||||
|
||||
describe('with propertiesToConsider', () => {
|
||||
it('should complain if none of the properties in the `propertiesToConsider` list is supported',
|
||||
() => {
|
||||
const propertiesToConsider = ['es1337', 'fesm42'];
|
||||
const errorMessage =
|
||||
'No supported format property to consider among [es1337, fesm42]. Supported ' +
|
||||
'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module';
|
||||
|
||||
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider}))
|
||||
.toThrowError(errorMessage);
|
||||
});
|
||||
|
||||
it('should only compile the entry-point formats given in the `propertiesToConsider` list',
|
||||
() => {
|
||||
mainNgcc({
|
||||
@ -210,6 +250,33 @@ runInEachFileSystem(() => {
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark all matching properties as processed in order not to compile them on a subsequent run',
|
||||
() => {
|
||||
const logger = new MockLogger();
|
||||
const logs = logger.logs.debug;
|
||||
|
||||
// `fesm2015` and `es2015` map to the same file: `./fesm2015/common.js`
|
||||
mainNgcc({
|
||||
basePath: '/node_modules/@angular/common',
|
||||
propertiesToConsider: ['fesm2015'], logger,
|
||||
});
|
||||
|
||||
expect(logs).not.toContain(['Skipping @angular/common : es2015 (already compiled).']);
|
||||
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
|
||||
es2015: '0.0.0-PLACEHOLDER',
|
||||
fesm2015: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
|
||||
// Now, compiling `es2015` should be a no-op.
|
||||
mainNgcc({
|
||||
basePath: '/node_modules/@angular/common',
|
||||
propertiesToConsider: ['es2015'], logger,
|
||||
});
|
||||
|
||||
expect(logs).toContain(['Skipping @angular/common : es2015 (already compiled).']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with compileAllFormats set to false', () => {
|
||||
@ -256,6 +323,7 @@ runInEachFileSystem(() => {
|
||||
|
||||
});
|
||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||
fesm5: '0.0.0-PLACEHOLDER',
|
||||
module: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
@ -268,6 +336,7 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||
esm5: '0.0.0-PLACEHOLDER',
|
||||
fesm5: '0.0.0-PLACEHOLDER',
|
||||
module: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
@ -313,6 +382,28 @@ runInEachFileSystem(() => {
|
||||
.toMatch(ANGULAR_CORE_IMPORT_REGEX);
|
||||
expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true);
|
||||
});
|
||||
|
||||
it('should update `package.json` for all matching format properties', () => {
|
||||
mainNgcc({
|
||||
basePath: '/node_modules/@angular/core',
|
||||
createNewEntryPointFormats: true,
|
||||
propertiesToConsider: ['fesm2015', 'fesm5'],
|
||||
});
|
||||
|
||||
const pkg: any = loadPackage('@angular/core');
|
||||
|
||||
// `es2015` is an alias of `fesm2015`.
|
||||
expect(pkg.fesm2015).toEqual('./fesm2015/core.js');
|
||||
expect(pkg.es2015).toEqual('./fesm2015/core.js');
|
||||
expect(pkg.fesm2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
|
||||
expect(pkg.es2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
|
||||
|
||||
// `module` is an alias of `fesm5`.
|
||||
expect(pkg.fesm5).toEqual('./fesm5/core.js');
|
||||
expect(pkg.module).toEqual('./fesm5/core.js');
|
||||
expect(pkg.fesm5_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
|
||||
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
|
||||
});
|
||||
});
|
||||
|
||||
describe('logger', () => {
|
||||
@ -342,6 +433,7 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||
es2015: '0.0.0-PLACEHOLDER',
|
||||
fesm2015: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({
|
||||
@ -399,6 +491,7 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||
es2015: '0.0.0-PLACEHOLDER',
|
||||
fesm2015: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
});
|
||||
@ -425,6 +518,7 @@ runInEachFileSystem(() => {
|
||||
// We process core but not core/testing.
|
||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||
es2015: '0.0.0-PLACEHOLDER',
|
||||
fesm2015: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
expect(loadPackage('@angular/core/testing').__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
@ -432,6 +526,7 @@ runInEachFileSystem(() => {
|
||||
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
|
||||
es2015: '0.0.0-PLACEHOLDER',
|
||||
fesm2015: '0.0.0-PLACEHOLDER',
|
||||
typings: '0.0.0-PLACEHOLDER',
|
||||
});
|
||||
});
|
||||
@ -482,6 +577,30 @@ runInEachFileSystem(() => {
|
||||
contents: `export declare class AppComponent {};`
|
||||
},
|
||||
]);
|
||||
|
||||
// An Angular package that has a missing dependency
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _('/node_modules/invalid-package/package.json'),
|
||||
contents: '{"name": "invalid-package", "es2015": "./index.js", "typings": "./index.d.ts"}'
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/invalid-package/index.js'),
|
||||
contents: `
|
||||
import {AppModule} from "@angular/missing";
|
||||
import {Component} from '@angular/core';
|
||||
export class AppComponent {};
|
||||
AppComponent.decorators = [
|
||||
{ type: Component, args: [{selector: 'app', template: '<h2>Hello</h2>'}] }
|
||||
];
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/invalid-package/index.d.ts'),
|
||||
contents: `export declare class AppComponent {}`
|
||||
},
|
||||
{name: _('/node_modules/invalid-package/index.metadata.json'), contents: 'DUMMY DATA'},
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -161,7 +161,7 @@ runInEachFileSystem(() => {
|
||||
loadFakeCore(getFileSystem());
|
||||
const errors: ts.Diagnostic[] = [];
|
||||
const rootFiles = getRootFiles(testFiles);
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
|
||||
const program = bundle.src.program;
|
||||
|
||||
const reflectionHost =
|
||||
|
@ -79,31 +79,73 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
describe('markAsProcessed', () => {
|
||||
it('should write a property in the package.json containing the version placeholder', () => {
|
||||
it('should write properties in the package.json containing the version placeholder', () => {
|
||||
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||
const fs = getFileSystem();
|
||||
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
expect(pkg.scripts).toBeUndefined();
|
||||
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
|
||||
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
|
||||
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5');
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
|
||||
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update the packageJson object in-place', () => {
|
||||
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||
const fs = getFileSystem();
|
||||
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
const pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.scripts).toBeUndefined();
|
||||
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
|
||||
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
|
||||
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||
});
|
||||
|
||||
it('should one perform one write operation for all updated properties', () => {
|
||||
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||
const fs = getFileSystem();
|
||||
const writeFileSpy = spyOn(fs, 'writeFile');
|
||||
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5', 'esm2015', 'esm5']);
|
||||
expect(writeFileSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it(`should keep backup of existing 'prepublishOnly' script`, () => {
|
||||
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||
const fs = getFileSystem();
|
||||
const prepublishOnly = 'existing script';
|
||||
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
pkg.scripts = {prepublishOnly};
|
||||
|
||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015']);
|
||||
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||
expect(pkg.scripts.prepublishOnly).toContain('This is not allowed');
|
||||
expect(pkg.scripts.prepublishOnly__ivy_ngcc_bak).toBe(prepublishOnly);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -144,8 +144,7 @@ runInEachFileSystem(() => {
|
||||
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
||||
compiledByAngular: true,
|
||||
};
|
||||
const esm5bundle =
|
||||
makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', 'esm5', true) !;
|
||||
const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true);
|
||||
|
||||
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
||||
.toEqual(jasmine.arrayWithExactContents([
|
||||
@ -192,8 +191,8 @@ runInEachFileSystem(() => {
|
||||
compiledByAngular: true,
|
||||
};
|
||||
const esm5bundle = makeEntryPointBundle(
|
||||
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
|
||||
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true) !;
|
||||
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
||||
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
|
||||
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
||||
.toContain(absoluteFrom('/node_modules/test/internal.js'));
|
||||
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
|
||||
@ -213,8 +212,8 @@ runInEachFileSystem(() => {
|
||||
compiledByAngular: true,
|
||||
};
|
||||
const esm5bundle = makeEntryPointBundle(
|
||||
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
|
||||
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false) !;
|
||||
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
||||
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false);
|
||||
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
||||
.toContain(absoluteFrom('/node_modules/test/internal.js'));
|
||||
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
|
||||
|
@ -149,8 +149,7 @@ exports.D = D;
|
||||
loadTestFiles([file]);
|
||||
const fs = getFileSystem();
|
||||
const logger = new MockLogger();
|
||||
const bundle =
|
||||
makeTestEntryPointBundle('test-package', 'module', 'commonjs', false, [file.name]);
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'commonjs', false, [file.name]);
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new CommonJsReflectionHost(logger, false, bundle.src.program, bundle.src.host);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
|
@ -61,8 +61,7 @@ function createTestRenderer(
|
||||
const fs = getFileSystem();
|
||||
const isCore = packageName === '@angular/core';
|
||||
const bundle = makeTestEntryPointBundle(
|
||||
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
|
||||
dtsFiles && getRootFiles(dtsFiles));
|
||||
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
|
@ -26,7 +26,7 @@ function setup(file: {name: AbsoluteFsPath, contents: string}) {
|
||||
loadTestFiles([file]);
|
||||
const fs = getFileSystem();
|
||||
const logger = new MockLogger();
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'module', 'esm5', false, [file.name]);
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm5ReflectionHost(logger, false, typeChecker);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
|
@ -31,8 +31,7 @@ function setup(files: TestFile[], dtsFiles?: TestFile[]) {
|
||||
}
|
||||
const logger = new MockLogger();
|
||||
const bundle = makeTestEntryPointBundle(
|
||||
'test-package', 'es2015', 'esm2015', false, getRootFiles(files),
|
||||
dtsFiles && getRootFiles(dtsFiles)) !;
|
||||
'test-package', 'esm2015', false, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles)) !;
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm2015ReflectionHost(logger, false, typeChecker, bundle.dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
|
@ -63,8 +63,7 @@ function createTestRenderer(
|
||||
const fs = getFileSystem();
|
||||
const isCore = packageName === '@angular/core';
|
||||
const bundle = makeTestEntryPointBundle(
|
||||
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
|
||||
dtsFiles && getRootFiles(dtsFiles));
|
||||
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
@ -98,6 +97,7 @@ runInEachFileSystem(() => {
|
||||
let _: typeof absoluteFrom;
|
||||
let INPUT_PROGRAM: TestFile;
|
||||
let COMPONENT_PROGRAM: TestFile;
|
||||
let NGMODULE_PROGRAM: TestFile;
|
||||
let INPUT_PROGRAM_MAP: SourceMapConverter;
|
||||
let RENDERED_CONTENTS: string;
|
||||
let OUTPUT_PROGRAM_MAP: SourceMapConverter;
|
||||
@ -118,6 +118,12 @@ runInEachFileSystem(() => {
|
||||
`import { Component } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: Component, args: [{ selector: 'a', template: '{{ person!.name }}' }] }\n];\n`
|
||||
};
|
||||
|
||||
NGMODULE_PROGRAM = {
|
||||
name: _('/node_modules/test-package/src/ngmodule.js'),
|
||||
contents:
|
||||
`import { NgModule } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: NgModule, args: [{}] }\n];\n`
|
||||
};
|
||||
|
||||
INPUT_PROGRAM_MAP = fromObject({
|
||||
'version': 3,
|
||||
'file': _('/node_modules/test-package/src/file.js'),
|
||||
@ -254,6 +260,25 @@ runInEachFileSystem(() => {
|
||||
.toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`);
|
||||
});
|
||||
|
||||
it('should render static fields before any additional statements', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
testFormatter} = createTestRenderer('test-package', [NGMODULE_PROGRAM]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
const definitions: string = addDefinitionsSpy.calls.first().args[2];
|
||||
const ngModuleDef = definitions.indexOf('ngModuleDef');
|
||||
expect(ngModuleDef).not.toEqual(-1, 'ngModuleDef should exist');
|
||||
const ngInjectorDef = definitions.indexOf('ngInjectorDef');
|
||||
expect(ngInjectorDef).not.toEqual(-1, 'ngInjectorDef should exist');
|
||||
const setClassMetadata = definitions.indexOf('setClassMetadata');
|
||||
expect(setClassMetadata).not.toEqual(-1, 'setClassMetadata call should exist');
|
||||
expect(setClassMetadata)
|
||||
.toBeGreaterThan(ngModuleDef, 'setClassMetadata should follow ngModuleDef');
|
||||
expect(setClassMetadata)
|
||||
.toBeGreaterThan(ngInjectorDef, 'setClassMetadata should follow ngInjectorDef');
|
||||
});
|
||||
|
||||
it('should render classes without decorators if handler matches', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
testFormatter} =
|
||||
@ -283,7 +308,7 @@ runInEachFileSystem(() => {
|
||||
ɵngcc0.ɵɵstaticViewQuery(_c0, true);
|
||||
} if (rf & 2) {
|
||||
var _t;
|
||||
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadViewQuery()) && (ctx.test = _t.first);
|
||||
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.test = _t.first);
|
||||
} } });`);
|
||||
});
|
||||
|
||||
|
@ -25,7 +25,7 @@ function setup(file: TestFile) {
|
||||
loadTestFiles([file]);
|
||||
const fs = getFileSystem();
|
||||
const logger = new MockLogger();
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'esm5', 'esm5', false, [file.name]);
|
||||
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
|
||||
const src = bundle.src;
|
||||
const host = new UmdReflectionHost(logger, false, src.program, src.host);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
|
@ -8,7 +8,6 @@
|
||||
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {loadTestFiles} from '../../../test/helpers';
|
||||
import {EntryPoint} from '../../src/packages/entry_point';
|
||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
|
||||
|
||||
@ -32,7 +31,7 @@ runInEachFileSystem(() => {
|
||||
it('should write all the FileInfo to the disk', () => {
|
||||
const fs = getFileSystem();
|
||||
const fileWriter = new InPlaceFileWriter(fs);
|
||||
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
|
||||
fileWriter.writeBundle({} as EntryPointBundle, [
|
||||
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
|
||||
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
|
||||
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
|
||||
@ -49,7 +48,7 @@ runInEachFileSystem(() => {
|
||||
it('should create backups of all files that previously existed', () => {
|
||||
const fs = getFileSystem();
|
||||
const fileWriter = new InPlaceFileWriter(fs);
|
||||
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
|
||||
fileWriter.writeBundle({} as EntryPointBundle, [
|
||||
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
|
||||
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
|
||||
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
|
||||
@ -72,10 +71,7 @@ runInEachFileSystem(() => {
|
||||
const absoluteBackupPath = _('/package/path/already-backed-up.js');
|
||||
expect(
|
||||
() => fileWriter.writeBundle(
|
||||
{} as EntryPoint, {} as EntryPointBundle,
|
||||
[
|
||||
{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'},
|
||||
]))
|
||||
{} as EntryPointBundle, [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]))
|
||||
.toThrowError(
|
||||
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
|
||||
});
|
||||
|
@ -32,8 +32,15 @@ runInEachFileSystem(() => {
|
||||
|
||||
{
|
||||
name: _('/node_modules/test/package.json'),
|
||||
contents:
|
||||
'{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
|
||||
contents: `
|
||||
{
|
||||
"module": "./esm5.js",
|
||||
"fesm2015": "./es2015/index.js",
|
||||
"fesm5": "./esm5.js",
|
||||
"es2015": "./es2015/index.js",
|
||||
"typings": "./index.d.ts"
|
||||
}
|
||||
`,
|
||||
},
|
||||
{name: _('/node_modules/test/index.d.ts'), contents: 'export declare class FooTop {}'},
|
||||
{name: _('/node_modules/test/index.d.ts.map'), contents: 'ORIGINAL MAPPING DATA'},
|
||||
@ -44,8 +51,15 @@ runInEachFileSystem(() => {
|
||||
{name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'},
|
||||
{
|
||||
name: _('/node_modules/test/a/package.json'),
|
||||
contents:
|
||||
`{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}`
|
||||
contents: `
|
||||
{
|
||||
"module": "./esm5.js",
|
||||
"fesm2015": "./es2015/index.js",
|
||||
"fesm5": "./esm5.js",
|
||||
"es2015": "./es2015/index.js",
|
||||
"typings": "./index.d.ts"
|
||||
}
|
||||
`,
|
||||
},
|
||||
{name: _('/node_modules/test/a/index.d.ts'), contents: 'export declare class FooA {}'},
|
||||
{name: _('/node_modules/test/a/index.metadata.json'), contents: '...'},
|
||||
@ -95,13 +109,16 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should write the modified files to a new folder', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/esm5.js'),
|
||||
contents: 'export function FooTop() {} // MODIFIED'
|
||||
},
|
||||
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/esm5.js'),
|
||||
contents: 'export function FooTop() {} // MODIFIED'
|
||||
},
|
||||
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
|
||||
],
|
||||
['module']);
|
||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js')))
|
||||
.toEqual('export function FooTop() {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}');
|
||||
@ -111,12 +128,15 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should also copy unmodified files in the program', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js')))
|
||||
.toEqual('export class FooTop {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/es2015/foo.js')))
|
||||
@ -128,36 +148,77 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should update the package.json properties', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/esm5.js'),
|
||||
contents: 'export function FooTop() {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/esm5.js'),
|
||||
contents: 'export function FooTop() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||
}));
|
||||
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
||||
}));
|
||||
});
|
||||
|
||||
it('should be able to update multiple package.json properties at once', () => {
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/esm5.js'),
|
||||
contents: 'export function FooTop() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module', 'fesm5']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||
}));
|
||||
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015', 'fesm2015']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
||||
fesm2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
||||
}));
|
||||
});
|
||||
|
||||
it('should overwrite and backup typings files', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/index.d.ts'),
|
||||
contents: 'export declare class FooTop {} // MODIFIED'
|
||||
},
|
||||
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/index.d.ts'),
|
||||
contents: 'export declare class FooTop {} // MODIFIED'
|
||||
},
|
||||
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.readFile(_('/node_modules/test/index.d.ts')))
|
||||
.toEqual('export declare class FooTop {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak')))
|
||||
@ -184,24 +245,30 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should write the modified file to a new folder', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/a/esm5.js'),
|
||||
contents: 'export function FooA() {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/esm5.js'),
|
||||
contents: 'export function FooA() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module']);
|
||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js')))
|
||||
.toEqual('export function FooA() {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}');
|
||||
});
|
||||
|
||||
it('should also copy unmodified files in the program', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||
contents: 'export class FooA {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||
contents: 'export class FooA {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js')))
|
||||
.toEqual('export class FooA {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js')))
|
||||
@ -213,35 +280,76 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should update the package.json properties', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/a/esm5.js'),
|
||||
contents: 'export function FooA() {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/esm5.js'),
|
||||
contents: 'export function FooA() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||
}));
|
||||
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||
contents: 'export class FooA {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||
contents: 'export class FooA {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
||||
}));
|
||||
});
|
||||
|
||||
it('should be able to update multiple package.json properties at once', () => {
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/esm5.js'),
|
||||
contents: 'export function FooA() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module', 'fesm5']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||
}));
|
||||
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||
contents: 'export class FooA {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015', 'fesm2015']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
||||
fesm2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
||||
}));
|
||||
});
|
||||
|
||||
it('should overwrite and backup typings files', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/a/index.d.ts'),
|
||||
contents: 'export declare class FooA {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/a/index.d.ts'),
|
||||
contents: 'export declare class FooA {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.readFile(_('/node_modules/test/a/index.d.ts')))
|
||||
.toEqual('export declare class FooA {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak')))
|
||||
@ -262,12 +370,15 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should write the modified file to a new folder', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/lib/esm5.js'),
|
||||
contents: 'export function FooB() {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/lib/esm5.js'),
|
||||
contents: 'export function FooB() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module']);
|
||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js')))
|
||||
.toEqual('export function FooB() {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/lib/esm5.js')))
|
||||
@ -275,12 +386,15 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should also copy unmodified files in the program', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js')))
|
||||
.toEqual('export class FooB {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js')))
|
||||
@ -293,43 +407,55 @@ runInEachFileSystem(() => {
|
||||
|
||||
it('should not copy typings files within the package (i.e. from a different entry-point)',
|
||||
() => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not copy files outside of the package', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false);
|
||||
expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should update the package.json properties', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/lib/esm5.js'),
|
||||
contents: 'export function FooB() {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/lib/esm5.js'),
|
||||
contents: 'export function FooB() {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['module']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
|
||||
}));
|
||||
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||
contents: 'export class FooB {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
|
||||
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
|
||||
es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js',
|
||||
@ -337,12 +463,15 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
it('should overwrite and backup typings files', () => {
|
||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
||||
{
|
||||
path: _('/node_modules/test/typings/index.d.ts'),
|
||||
contents: 'export declare class FooB {} // MODIFIED'
|
||||
},
|
||||
]);
|
||||
fileWriter.writeBundle(
|
||||
esm2015bundle,
|
||||
[
|
||||
{
|
||||
path: _('/node_modules/test/typings/index.d.ts'),
|
||||
contents: 'export declare class FooB {} // MODIFIED'
|
||||
},
|
||||
],
|
||||
['es2015']);
|
||||
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts')))
|
||||
.toEqual('export declare class FooB {} // MODIFIED');
|
||||
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak')))
|
||||
@ -356,7 +485,6 @@ runInEachFileSystem(() => {
|
||||
fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty,
|
||||
format: EntryPointFormat): EntryPointBundle {
|
||||
return makeEntryPointBundle(
|
||||
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, formatProperty, format,
|
||||
true) !;
|
||||
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, format, true);
|
||||
}
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ import {DirectiveMeta, MetadataReader, MetadataRegistry, extractDirectiveGuards}
|
||||
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry} from '../../scope';
|
||||
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
import {TypeCheckContext} from '../../typecheck';
|
||||
import {NoopResourceDependencyRecorder, ResourceDependencyRecorder} from '../../util/src/resource_recorder';
|
||||
@ -47,8 +47,8 @@ export class ComponentDecoratorHandler implements
|
||||
constructor(
|
||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||
private metaRegistry: MetadataRegistry, private metaReader: MetadataReader,
|
||||
private scopeRegistry: LocalModuleScopeRegistry, private isCore: boolean,
|
||||
private resourceLoader: ResourceLoader, private rootDirs: string[],
|
||||
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private isCore: boolean, private resourceLoader: ResourceLoader, private rootDirs: string[],
|
||||
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
|
||||
private moduleResolver: ModuleResolver, private cycleAnalyzer: CycleAnalyzer,
|
||||
private refEmitter: ReferenceEmitter, private defaultImportRecorder: DefaultImportRecorder,
|
||||
@ -327,7 +327,7 @@ export class ComponentDecoratorHandler implements
|
||||
preserveWhitespaces: true,
|
||||
leadingTriviaChars: [],
|
||||
});
|
||||
const scope = this.scopeRegistry.getScopeForComponent(node);
|
||||
const scope = this.scopeReader.getScopeForComponent(node);
|
||||
const selector = analysis.meta.selector;
|
||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||
if (scope !== null) {
|
||||
@ -353,7 +353,7 @@ export class ComponentDecoratorHandler implements
|
||||
if (!ts.isClassDeclaration(node)) {
|
||||
return;
|
||||
}
|
||||
const scope = this.scopeRegistry.getScopeForComponent(node);
|
||||
const scope = this.scopeReader.getScopeForComponent(node);
|
||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||
if (scope !== null) {
|
||||
for (const meta of scope.compilation.directives) {
|
||||
@ -377,7 +377,7 @@ export class ComponentDecoratorHandler implements
|
||||
const context = node.getSourceFile();
|
||||
// Check whether this component was registered with an NgModule. If so, it should be compiled
|
||||
// under that module's compilation scope.
|
||||
const scope = this.scopeRegistry.getScopeForComponent(node);
|
||||
const scope = this.scopeReader.getScopeForComponent(node);
|
||||
let metadata = analysis.meta;
|
||||
if (scope !== null) {
|
||||
// Replace the empty components and directives from the analyze() step with a fully expanded
|
||||
|
@ -60,7 +60,7 @@ runInEachFileSystem(() => {
|
||||
const refEmitter = new ReferenceEmitter([]);
|
||||
|
||||
const handler = new ComponentDecoratorHandler(
|
||||
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, false,
|
||||
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry, false,
|
||||
new NoopResourceLoader(), [''], false, true, moduleResolver, cycleAnalyzer, refEmitter,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
|
||||
|
@ -12,6 +12,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
@ -12,13 +12,14 @@ import {Reference} from '../../imports';
|
||||
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
|
||||
import {DependencyTracker} from '../../partial_evaluator';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ComponentScopeReader, ComponentScopeRegistry, LocalModuleScope} from '../../scope';
|
||||
import {ResourceDependencyRecorder} from '../../util/src/resource_recorder';
|
||||
|
||||
/**
|
||||
* Accumulates state between compilations.
|
||||
*/
|
||||
export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry,
|
||||
ResourceDependencyRecorder {
|
||||
ResourceDependencyRecorder, ComponentScopeRegistry, ComponentScopeReader {
|
||||
private constructor(
|
||||
private unchangedFiles: Set<ts.SourceFile>,
|
||||
private metadata: Map<ts.SourceFile, FileMetadata>,
|
||||
@ -69,32 +70,56 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
|
||||
}
|
||||
|
||||
getFileDependencies(file: ts.SourceFile): ts.SourceFile[] {
|
||||
const meta = this.metadata.get(file);
|
||||
return meta ? Array.from(meta.fileDependencies) : [];
|
||||
if (!this.metadata.has(file)) {
|
||||
return [];
|
||||
}
|
||||
const meta = this.metadata.get(file) !;
|
||||
return Array.from(meta.fileDependencies);
|
||||
}
|
||||
|
||||
getNgModuleMetadata(ref: Reference<ClassDeclaration>): NgModuleMeta|null {
|
||||
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
|
||||
return metadata && metadata.ngModuleMeta.get(ref.node) || null;
|
||||
if (!this.metadata.has(ref.node.getSourceFile())) {
|
||||
return null;
|
||||
}
|
||||
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
|
||||
if (!metadata.ngModuleMeta.has(ref.node)) {
|
||||
return null;
|
||||
}
|
||||
return metadata.ngModuleMeta.get(ref.node) !;
|
||||
}
|
||||
|
||||
registerNgModuleMetadata(meta: NgModuleMeta): void {
|
||||
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
|
||||
metadata.ngModuleMeta.set(meta.ref.node, meta);
|
||||
}
|
||||
|
||||
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
|
||||
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
|
||||
return metadata && metadata.directiveMeta.get(ref.node) || null;
|
||||
if (!this.metadata.has(ref.node.getSourceFile())) {
|
||||
return null;
|
||||
}
|
||||
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
|
||||
if (!metadata.directiveMeta.has(ref.node)) {
|
||||
return null;
|
||||
}
|
||||
return metadata.directiveMeta.get(ref.node) !;
|
||||
}
|
||||
|
||||
registerDirectiveMetadata(meta: DirectiveMeta): void {
|
||||
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
|
||||
metadata.directiveMeta.set(meta.ref.node, meta);
|
||||
}
|
||||
|
||||
getPipeMetadata(ref: Reference<ClassDeclaration>): PipeMeta|null {
|
||||
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
|
||||
return metadata && metadata.pipeMeta.get(ref.node) || null;
|
||||
if (!this.metadata.has(ref.node.getSourceFile())) {
|
||||
return null;
|
||||
}
|
||||
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
|
||||
if (!metadata.pipeMeta.has(ref.node)) {
|
||||
return null;
|
||||
}
|
||||
return metadata.pipeMeta.get(ref.node) !;
|
||||
}
|
||||
|
||||
registerPipeMetadata(meta: PipeMeta): void {
|
||||
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
|
||||
metadata.pipeMeta.set(meta.ref.node, meta);
|
||||
@ -105,6 +130,40 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
|
||||
metadata.resourcePaths.add(resourcePath);
|
||||
}
|
||||
|
||||
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void {
|
||||
const metadata = this.ensureMetadata(clazz.getSourceFile());
|
||||
metadata.componentScope.set(clazz, scope);
|
||||
}
|
||||
|
||||
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
|
||||
if (!this.metadata.has(clazz.getSourceFile())) {
|
||||
return null;
|
||||
}
|
||||
const metadata = this.metadata.get(clazz.getSourceFile()) !;
|
||||
if (!metadata.componentScope.has(clazz)) {
|
||||
return null;
|
||||
}
|
||||
return metadata.componentScope.get(clazz) !;
|
||||
}
|
||||
|
||||
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void {
|
||||
const metadata = this.ensureMetadata(clazz.getSourceFile());
|
||||
metadata.remoteScoping.add(clazz);
|
||||
}
|
||||
|
||||
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null {
|
||||
// TODO: https://angular-team.atlassian.net/browse/FW-1501
|
||||
// Handle the incremental build case where a component requires remote scoping.
|
||||
// This means that if the the component's template changes, it requires the module to be
|
||||
// re-emitted.
|
||||
// Also, we need to make sure the cycle detector works well across rebuilds.
|
||||
if (!this.metadata.has(clazz.getSourceFile())) {
|
||||
return null;
|
||||
}
|
||||
const metadata = this.metadata.get(clazz.getSourceFile()) !;
|
||||
return metadata.remoteScoping.has(clazz);
|
||||
}
|
||||
|
||||
private ensureMetadata(sf: ts.SourceFile): FileMetadata {
|
||||
const metadata = this.metadata.get(sf) || new FileMetadata();
|
||||
this.metadata.set(sf, metadata);
|
||||
@ -131,4 +190,6 @@ class FileMetadata {
|
||||
directiveMeta = new Map<ClassDeclaration, DirectiveMeta>();
|
||||
ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>();
|
||||
pipeMeta = new Map<ClassDeclaration, PipeMeta>();
|
||||
componentScope = new Map<ClassDeclaration, LocalModuleScope>();
|
||||
remoteScoping = new Set<ClassDeclaration>();
|
||||
}
|
||||
|
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));
|
||||
|
@ -28,7 +28,7 @@ import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
||||
import {TypeScriptReflectionHost} from './reflection';
|
||||
import {HostResourceLoader} from './resource_loader';
|
||||
import {NgModuleRouteAnalyzer, entryPointKeyFor} from './routing';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
|
||||
import {CompoundComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
|
||||
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, TypeCheckShimGenerator, generatedFactoryTransform} from './shims';
|
||||
import {ivySwitchTransform} from './switch';
|
||||
import {IvyCompilation, declarationTransformFactory, ivyTransformFactory} from './transform';
|
||||
@ -476,7 +476,8 @@ export class NgtscProgram implements api.Program {
|
||||
const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]);
|
||||
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator);
|
||||
const scopeRegistry = new LocalModuleScopeRegistry(
|
||||
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator);
|
||||
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator, this.incrementalState);
|
||||
const scopeReader = new CompoundComponentScopeReader([scopeRegistry, this.incrementalState]);
|
||||
const metaRegistry =
|
||||
new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]);
|
||||
|
||||
@ -502,10 +503,11 @@ export class NgtscProgram implements api.Program {
|
||||
const handlers = [
|
||||
new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore),
|
||||
new ComponentDecoratorHandler(
|
||||
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeRegistry, this.isCore,
|
||||
this.resourceManager, this.rootDirs, this.options.preserveWhitespaces || false,
|
||||
this.options.i18nUseExternalIds !== false, this.moduleResolver, this.cycleAnalyzer,
|
||||
this.refEmitter, this.defaultImportTracker, this.incrementalState),
|
||||
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeReader, scopeRegistry,
|
||||
this.isCore, this.resourceManager, this.rootDirs,
|
||||
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
|
||||
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, this.defaultImportTracker,
|
||||
this.incrementalState),
|
||||
new DirectiveDecoratorHandler(
|
||||
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
|
||||
new InjectableDecoratorHandler(
|
||||
|
@ -7,5 +7,6 @@
|
||||
*/
|
||||
|
||||
export {ExportScope, ScopeData} from './src/api';
|
||||
export {ComponentScopeReader, ComponentScopeRegistry, CompoundComponentScopeReader} from './src/component_scope';
|
||||
export {DtsModuleScopeResolver, MetadataDtsModuleScopeResolver} from './src/dependency';
|
||||
export {LocalModuleScope, LocalModuleScopeRegistry, LocalNgModuleData} from './src/local';
|
||||
|
67
packages/compiler-cli/src/ngtsc/scope/src/component_scope.ts
Normal file
67
packages/compiler-cli/src/ngtsc/scope/src/component_scope.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {LocalModuleScope} from './local';
|
||||
|
||||
/**
|
||||
* Register information about the compilation scope of components.
|
||||
*/
|
||||
export interface ComponentScopeRegistry {
|
||||
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void;
|
||||
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read information about the compilation scope of components.
|
||||
*/
|
||||
export interface ComponentScopeReader {
|
||||
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null;
|
||||
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A noop registry that doesn't do anything.
|
||||
*
|
||||
* This can be used in tests and cases where we don't care about the compilation scopes
|
||||
* being registered.
|
||||
*/
|
||||
export class NoopComponentScopeRegistry implements ComponentScopeRegistry {
|
||||
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void {}
|
||||
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ComponentScopeReader` that reads from an ordered set of child readers until it obtains the
|
||||
* requested scope.
|
||||
*
|
||||
* This is used to combine `ComponentScopeReader`s that read from different sources (e.g. from a
|
||||
* registry and from the incremental state).
|
||||
*/
|
||||
export class CompoundComponentScopeReader implements ComponentScopeReader {
|
||||
constructor(private readers: ComponentScopeReader[]) {}
|
||||
|
||||
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
|
||||
for (const reader of this.readers) {
|
||||
const meta = reader.getScopeForComponent(clazz);
|
||||
if (meta !== null) {
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null {
|
||||
for (const reader of this.readers) {
|
||||
const requiredScoping = reader.getRequiresRemoteScope(clazz);
|
||||
if (requiredScoping !== null) {
|
||||
return requiredScoping;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -11,11 +11,12 @@ import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
|
||||
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {DirectiveMeta, LocalMetadataRegistry, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
|
||||
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
|
||||
|
||||
import {ExportScope, ScopeData} from './api';
|
||||
import {ComponentScopeReader, ComponentScopeRegistry, NoopComponentScopeRegistry} from './component_scope';
|
||||
import {DtsModuleScopeResolver} from './dependency';
|
||||
|
||||
export interface LocalNgModuleData {
|
||||
@ -58,7 +59,7 @@ export interface CompilationScope extends ScopeData {
|
||||
* The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
|
||||
* semantics are violated.
|
||||
*/
|
||||
export class LocalModuleScopeRegistry implements MetadataRegistry {
|
||||
export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScopeReader {
|
||||
/**
|
||||
* Tracks whether the registry has been asked to produce scopes for a module or component. Once
|
||||
* this is true, the registry cannot accept registrations of new directives/pipes/modules as it
|
||||
@ -102,7 +103,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
|
||||
|
||||
constructor(
|
||||
private localReader: MetadataReader, private dependencyScopeReader: DtsModuleScopeResolver,
|
||||
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null) {}
|
||||
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null,
|
||||
private componentScopeRegistry: ComponentScopeRegistry = new NoopComponentScopeRegistry()) {}
|
||||
|
||||
/**
|
||||
* Add an NgModule's data to the registry.
|
||||
@ -120,10 +122,13 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
|
||||
registerPipeMetadata(pipe: PipeMeta): void {}
|
||||
|
||||
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
|
||||
if (!this.declarationToModule.has(clazz)) {
|
||||
return null;
|
||||
const scope = !this.declarationToModule.has(clazz) ?
|
||||
null :
|
||||
this.getScopeOfModule(this.declarationToModule.get(clazz) !);
|
||||
if (scope !== null) {
|
||||
this.componentScopeRegistry.registerComponentScope(clazz, scope);
|
||||
}
|
||||
return this.getScopeOfModule(this.declarationToModule.get(clazz) !);
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -383,6 +388,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
|
||||
*/
|
||||
setComponentAsRequiringRemoteScoping(node: ClassDeclaration): void {
|
||||
this.remoteScoping.add(node);
|
||||
this.componentScopeRegistry.setComponentAsRequiringRemoteScoping(node);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1535,8 +1535,8 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDirs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
|
||||
}
|
||||
},
|
||||
consts: 1,
|
||||
@ -1593,8 +1593,8 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
|
||||
}
|
||||
},
|
||||
…
|
||||
@ -1646,8 +1646,8 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.foo = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.foo = $tmp$.first);
|
||||
}
|
||||
},
|
||||
consts: 1,
|
||||
@ -1711,10 +1711,10 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDirs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
|
||||
}
|
||||
},
|
||||
…
|
||||
@ -1775,8 +1775,8 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDirList = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirList = $tmp$);
|
||||
}
|
||||
},
|
||||
ngContentSelectors: _c0,
|
||||
@ -1835,8 +1835,8 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
|
||||
}
|
||||
},
|
||||
…
|
||||
@ -1897,8 +1897,8 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.foo = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.foo = $tmp$.first);
|
||||
}
|
||||
},
|
||||
ngContentSelectors: $_c1$,
|
||||
@ -1964,10 +1964,10 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDirs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
|
||||
}
|
||||
},
|
||||
…
|
||||
@ -3144,7 +3144,7 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -3189,7 +3189,7 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -3232,7 +3232,7 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -3277,7 +3277,7 @@ describe('compiler compliance', () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
var $tmp$;
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -2600,14 +2600,16 @@ describe('i18n support in the view compiler', () => {
|
||||
function MyComponent_div_2_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", $_c2$);
|
||||
$r3$.ɵɵi18n(1, $I18N_3$);
|
||||
i0.ɵɵtext(1, " ");
|
||||
$r3$.ɵɵi18n(2, $I18N_3$);
|
||||
i0.ɵɵtext(3, " ");
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $ctx_r0$ = $r3$.ɵɵnextContext();
|
||||
$r3$.ɵɵselect(1);
|
||||
$r3$.ɵɵselect(2);
|
||||
$r3$.ɵɵi18nExp($ctx_r0$.age);
|
||||
$r3$.ɵɵi18nApply(1);
|
||||
$r3$.ɵɵi18nApply(2);
|
||||
}
|
||||
}
|
||||
const $_c3$ = ["title", "icu and text"];
|
||||
@ -2646,7 +2648,7 @@ describe('i18n support in the view compiler', () => {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵi18n(1, $I18N_0$);
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 2, 1, "div", $_c0$);
|
||||
$r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 4, 1, "div", $_c0$);
|
||||
$r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c1$);
|
||||
}
|
||||
if (rf & 2) {
|
||||
@ -2730,7 +2732,7 @@ describe('i18n support in the view compiler', () => {
|
||||
const $_c2$ = [1, "other"];
|
||||
var $I18N_0$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
|
||||
const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
|
||||
"startBoldText": "\uFFFD#2\uFFFD",
|
||||
"closeBoldText": "\uFFFD/#2\uFFFD",
|
||||
"startTagDiv": "\uFFFD#3\uFFFD",
|
||||
@ -2742,7 +2744,7 @@ describe('i18n support in the view compiler', () => {
|
||||
$I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
|
||||
"startBoldText": "\uFFFD#2\uFFFD",
|
||||
"closeBoldText": "\uFFFD/#2\uFFFD",
|
||||
"startTagDiv": "\uFFFD#3\uFFFD",
|
||||
@ -2848,14 +2850,14 @@ describe('i18n support in the view compiler', () => {
|
||||
});
|
||||
var $I18N_0$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$icu_1}", {
|
||||
const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$icu_1} ", {
|
||||
"icu": $I18N_1$,
|
||||
"icu_1": $I18N_2$
|
||||
});
|
||||
$I18N_0$ = $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$icu_1}", {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$icu_1} ", {
|
||||
"icu": $I18N_1$,
|
||||
"icu_1": $I18N_2$
|
||||
});
|
||||
@ -2930,7 +2932,7 @@ describe('i18n support in the view compiler', () => {
|
||||
});
|
||||
var $I18N_0$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", {
|
||||
const $MSG_APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", {
|
||||
"startTagDiv": "\uFFFD#2\uFFFD",
|
||||
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
|
||||
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
|
||||
@ -2939,7 +2941,7 @@ describe('i18n support in the view compiler', () => {
|
||||
$I18N_0$ = $MSG_APP_SPEC_TS_0$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", {
|
||||
"startTagDiv": "\uFFFD#2\uFFFD",
|
||||
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
|
||||
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
|
||||
@ -3000,19 +3002,26 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
var $I18N_0$;
|
||||
var $I18N_1$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
|
||||
$I18N_0$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$;
|
||||
$I18N_1$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
|
||||
$I18N_1$ = $r3$.ɵɵi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
|
||||
}
|
||||
$I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, {
|
||||
$I18N_1$ = $r3$.ɵɵi18nPostprocess($I18N_1$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD",
|
||||
"VAR_SELECT_1": "\uFFFD1\uFFFD"
|
||||
});
|
||||
…
|
||||
var $I18N_0$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_3052001905251380936$$APP_SPEC_TS_3$ = goog.getMsg(" {$icu} ", { "icu": $I18N_1$ });
|
||||
$I18N_0$ = $MSG_EXTERNAL_3052001905251380936$$APP_SPEC_TS_3$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = i0.ɵɵi18nLocalize(" {$icu} ", { "icu": $I18N_1$ });
|
||||
} …
|
||||
consts: 2,
|
||||
vars: 2,
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
@ -3117,7 +3126,7 @@ describe('i18n support in the view compiler', () => {
|
||||
});
|
||||
var $I18N_0$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
|
||||
const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
|
||||
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
|
||||
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
|
||||
"icu": $I18N_1$,
|
||||
@ -3126,7 +3135,7 @@ describe('i18n support in the view compiler', () => {
|
||||
$I18N_0$ = $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
|
||||
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
|
||||
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
|
||||
"icu": $I18N_1$,
|
||||
@ -3207,7 +3216,7 @@ describe('i18n support in the view compiler', () => {
|
||||
});
|
||||
var $I18N_0$;
|
||||
if (ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
|
||||
const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
|
||||
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
|
||||
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
|
||||
"icu": $I18N_1$,
|
||||
@ -3216,7 +3225,7 @@ describe('i18n support in the view compiler', () => {
|
||||
$I18N_0$ = $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
|
||||
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
|
||||
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
|
||||
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
|
||||
"icu": $I18N_1$,
|
||||
|
@ -195,6 +195,25 @@ runInEachFileSystem(() => {
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild only a Component (but with the correct CompilationScope) if its template has changed',
|
||||
() => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Make a change to the template of BarComponent.
|
||||
env.write('bar_component.html', '<div bar>changed</div>');
|
||||
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).not.toContain('/bar_directive.js');
|
||||
expect(written).toContain('/bar_component.js');
|
||||
expect(written).not.toContain('/bar_module.js');
|
||||
expect(written).not.toContain('/foo_component.js');
|
||||
expect(written).not.toContain('/foo_pipe.js');
|
||||
expect(written).not.toContain('/foo_module.js');
|
||||
// Ensure that the used directives are included in the component's generated template.
|
||||
expect(env.getContents('/built/bar_component.js')).toMatch(/directives:\s*\[.+\.BarDir\]/);
|
||||
});
|
||||
|
||||
it('should rebuild everything if a typings file changes', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
@ -280,9 +299,10 @@ runInEachFileSystem(() => {
|
||||
env.write('bar_component.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'bar', template: 'bar'})
|
||||
@Component({selector: 'bar', templateUrl: './bar_component.html'})
|
||||
export class BarCmp {}
|
||||
`);
|
||||
env.write('bar_component.html', '<div bar></div>');
|
||||
env.write('bar_directive.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
|
@ -60,18 +60,20 @@ export class WhitespaceVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
return new html.Element(
|
||||
element.name, element.attrs, html.visitAll(this, element.children), element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan, element.i18n);
|
||||
element.name, element.attrs, visitAllWithSiblings(this, element.children),
|
||||
element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
|
||||
}
|
||||
|
||||
visitAttribute(attribute: html.Attribute, context: any): any {
|
||||
return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null;
|
||||
}
|
||||
|
||||
visitText(text: html.Text, context: any): any {
|
||||
visitText(text: html.Text, context: SiblingVisitorContext|null): any {
|
||||
const isNotBlank = text.value.match(NO_WS_REGEXP);
|
||||
const hasExpansionSibling = context &&
|
||||
(context.prev instanceof html.Expansion || context.next instanceof html.Expansion);
|
||||
|
||||
if (isNotBlank) {
|
||||
if (isNotBlank || hasExpansionSibling) {
|
||||
return new html.Text(
|
||||
replaceNgsp(text.value).replace(WS_REPLACE_REGEXP, ' '), text.sourceSpan, text.i18n);
|
||||
}
|
||||
@ -91,3 +93,21 @@ export function removeWhitespaces(htmlAstWithErrors: ParseTreeResult): ParseTree
|
||||
html.visitAll(new WhitespaceVisitor(), htmlAstWithErrors.rootNodes),
|
||||
htmlAstWithErrors.errors);
|
||||
}
|
||||
|
||||
interface SiblingVisitorContext {
|
||||
prev: html.Node|undefined;
|
||||
next: html.Node|undefined;
|
||||
}
|
||||
|
||||
function visitAllWithSiblings(visitor: WhitespaceVisitor, nodes: html.Node[]): any[] {
|
||||
const result: any[] = [];
|
||||
|
||||
nodes.forEach((ast, i) => {
|
||||
const context: SiblingVisitorContext = {prev: nodes[i - 1], next: nodes[i + 1]};
|
||||
const astResult = ast.visit(visitor, context);
|
||||
if (astResult) {
|
||||
result.push(astResult);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
@ -202,8 +202,6 @@ export class Identifiers {
|
||||
static i18nPostprocess: o.ExternalReference = {name: 'ɵɵi18nPostprocess', moduleName: CORE};
|
||||
static i18nLocalize: o.ExternalReference = {name: 'ɵɵi18nLocalize', moduleName: CORE};
|
||||
|
||||
static load: o.ExternalReference = {name: 'ɵɵload', moduleName: CORE};
|
||||
|
||||
static pipe: o.ExternalReference = {name: 'ɵɵpipe', moduleName: CORE};
|
||||
|
||||
static projection: o.ExternalReference = {name: 'ɵɵprojection', moduleName: CORE};
|
||||
@ -279,9 +277,8 @@ export class Identifiers {
|
||||
static viewQuery: o.ExternalReference = {name: 'ɵɵviewQuery', moduleName: CORE};
|
||||
static staticViewQuery: o.ExternalReference = {name: 'ɵɵstaticViewQuery', moduleName: CORE};
|
||||
static staticContentQuery: o.ExternalReference = {name: 'ɵɵstaticContentQuery', moduleName: CORE};
|
||||
static loadViewQuery: o.ExternalReference = {name: 'ɵɵloadViewQuery', moduleName: CORE};
|
||||
static loadQuery: o.ExternalReference = {name: 'ɵɵloadQuery', moduleName: CORE};
|
||||
static contentQuery: o.ExternalReference = {name: 'ɵɵcontentQuery', moduleName: CORE};
|
||||
static loadContentQuery: o.ExternalReference = {name: 'ɵɵloadContentQuery', moduleName: CORE};
|
||||
|
||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵɵNgOnChangesFeature', moduleName: CORE};
|
||||
|
||||
|
@ -487,9 +487,9 @@ function createContentQueriesFunction(
|
||||
.callFn([o.variable('dirIndex'), ...prepareQueryParams(query, constantPool) as any])
|
||||
.toStmt());
|
||||
|
||||
// update, e.g. (r3.queryRefresh(tmp = r3.loadContentQuery()) && (ctx.someDir = tmp));
|
||||
// update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
|
||||
const temporary = tempAllocator();
|
||||
const getQueryList = o.importExpr(R3.loadContentQuery).callFn([]);
|
||||
const getQueryList = o.importExpr(R3.loadQuery).callFn([]);
|
||||
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
|
||||
const updateDirective = o.variable(CONTEXT_NAME)
|
||||
.prop(query.propertyName)
|
||||
@ -561,9 +561,9 @@ function createViewQueriesFunction(
|
||||
o.importExpr(queryInstruction).callFn(prepareQueryParams(query, constantPool));
|
||||
createStatements.push(queryDefinition.toStmt());
|
||||
|
||||
// update, e.g. (r3.queryRefresh(tmp = r3.loadViewQuery()) && (ctx.someDir = tmp));
|
||||
// update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
|
||||
const temporary = tempAllocator();
|
||||
const getQueryList = o.importExpr(R3.loadViewQuery).callFn([]);
|
||||
const getQueryList = o.importExpr(R3.loadQuery).callFn([]);
|
||||
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
|
||||
const updateDirective = o.variable(CONTEXT_NAME)
|
||||
.prop(query.propertyName)
|
||||
|
@ -40,7 +40,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
it('should not create a message for plain elements',
|
||||
() => { expect(_humanizeMessages('<div></div>')).toEqual([]); });
|
||||
|
||||
it('should suppoprt void elements', () => {
|
||||
it('should support void elements', () => {
|
||||
expect(_humanizeMessages('<div i18n="m|d"><p><br></p></div>')).toEqual([
|
||||
[
|
||||
[
|
||||
@ -173,6 +173,13 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract as ICU + ph when wrapped in whitespace in an element', () => {
|
||||
expect(_humanizeMessages('<div i18n="m|d"> {count, plural, =0 {zero}} </div>')).toEqual([
|
||||
[[' ', '<ph icu name="ICU">{count, plural, =0 {[zero]}}</ph>', ' '], 'm', 'd'],
|
||||
[['{count, plural, =0 {[zero]}}'], '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract as ICU when single child of a block', () => {
|
||||
expect(_humanizeMessages('<!-- i18n:m|d -->{count, plural, =0 {zero}}<!-- /i18n -->'))
|
||||
.toEqual([
|
||||
|
@ -9,14 +9,15 @@
|
||||
import * as html from '../../src/ml_parser/ast';
|
||||
import {HtmlParser} from '../../src/ml_parser/html_parser';
|
||||
import {PRESERVE_WS_ATTR_NAME, removeWhitespaces} from '../../src/ml_parser/html_whitespaces';
|
||||
import {TokenizeOptions} from '../../src/ml_parser/lexer';
|
||||
|
||||
import {humanizeDom} from './ast_spec_utils';
|
||||
|
||||
{
|
||||
describe('removeWhitespaces', () => {
|
||||
|
||||
function parseAndRemoveWS(template: string): any[] {
|
||||
return humanizeDom(removeWhitespaces(new HtmlParser().parse(template, 'TestComp')));
|
||||
function parseAndRemoveWS(template: string, options?: TokenizeOptions): any[] {
|
||||
return humanizeDom(removeWhitespaces(new HtmlParser().parse(template, 'TestComp', options)));
|
||||
}
|
||||
|
||||
it('should remove blank text nodes', () => {
|
||||
@ -97,6 +98,17 @@ import {humanizeDom} from './ast_spec_utils';
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve whitespaces around ICU expansions', () => {
|
||||
expect(parseAndRemoveWS(`<span> {a, b, =4 {c}} </span>`, {tokenizeExpansionForms: true}))
|
||||
.toEqual([
|
||||
[html.Element, 'span', 0],
|
||||
[html.Text, ' ', 1],
|
||||
[html.Expansion, 'a', 'b', 1],
|
||||
[html.ExpansionCase, '=4', 2],
|
||||
[html.Text, ' ', 1],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve whitespaces inside <pre> elements', () => {
|
||||
expect(parseAndRemoveWS(`<pre><strong>foo</strong>\n<strong>bar</strong></pre>`)).toEqual([
|
||||
[html.Element, 'pre', 0],
|
||||
|
@ -798,6 +798,30 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse an expansion form with whitespace surrounding it', () => {
|
||||
expect(tokenizeAndHumanizeParts(
|
||||
'<div><span> {a, b, =4 {c}} </span></div>', {tokenizeExpansionForms: true}))
|
||||
.toEqual([
|
||||
[lex.TokenType.TAG_OPEN_START, '', 'div'],
|
||||
[lex.TokenType.TAG_OPEN_END],
|
||||
[lex.TokenType.TAG_OPEN_START, '', 'span'],
|
||||
[lex.TokenType.TAG_OPEN_END],
|
||||
[lex.TokenType.TEXT, ' '],
|
||||
[lex.TokenType.EXPANSION_FORM_START],
|
||||
[lex.TokenType.RAW_TEXT, 'a'],
|
||||
[lex.TokenType.RAW_TEXT, 'b'],
|
||||
[lex.TokenType.EXPANSION_CASE_VALUE, '=4'],
|
||||
[lex.TokenType.EXPANSION_CASE_EXP_START],
|
||||
[lex.TokenType.TEXT, 'c'],
|
||||
[lex.TokenType.EXPANSION_CASE_EXP_END],
|
||||
[lex.TokenType.EXPANSION_FORM_END],
|
||||
[lex.TokenType.TEXT, ' '],
|
||||
[lex.TokenType.TAG_CLOSE, '', 'span'],
|
||||
[lex.TokenType.TAG_CLOSE, '', 'div'],
|
||||
[lex.TokenType.EOF],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse an expansion forms with elements in it', () => {
|
||||
expect(tokenizeAndHumanizeParts(
|
||||
'{one.two, three, =4 {four <b>a</b>}}', {tokenizeExpansionForms: true}))
|
||||
|
@ -4,11 +4,12 @@ ts_library(
|
||||
name = "google3",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
visibility = ["//packages/core/schematics/test:__pkg__"],
|
||||
visibility = ["//packages/core/schematics/test/google3:__pkg__"],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/injectable-pipe",
|
||||
"//packages/core/schematics/migrations/missing-injectable",
|
||||
"//packages/core/schematics/migrations/missing-injectable/google3",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
"//packages/core/schematics/migrations/static-queries",
|
||||
"//packages/core/schematics/migrations/template-var-assignment",
|
||||
"//packages/core/schematics/utils",
|
||||
|
@ -9,9 +9,9 @@
|
||||
import {Replacement, RuleFailure, Rules} from 'tslint';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {HelperFunction, getHelper} from '../helpers';
|
||||
import {migrateExpression, replaceImport} from '../migration';
|
||||
import {findCoreImport, findRendererReferences} from '../util';
|
||||
import {HelperFunction, getHelper} from '../renderer-to-renderer2/helpers';
|
||||
import {migrateExpression, replaceImport} from '../renderer-to-renderer2/migration';
|
||||
import {findCoreImport, findRendererReferences} from '../renderer-to-renderer2/util';
|
||||
|
||||
/**
|
||||
* TSLint rule that migrates from `Renderer` to `Renderer2`. More information on how it works:
|
@ -6,7 +6,7 @@ ts_library(
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
visibility = [
|
||||
"//packages/core/schematics:__pkg__",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2/google3:__pkg__",
|
||||
"//packages/core/schematics/migrations/google3:__pkg__",
|
||||
"//packages/core/schematics/test:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
|
@ -1,13 +0,0 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "google3",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
visibility = ["//packages/core/schematics/test:__pkg__"],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
"@npm//tslint",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
@ -9,12 +9,10 @@ ts_library(
|
||||
"//packages/core/schematics:migrations.json",
|
||||
],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/google3",
|
||||
"//packages/core/schematics/migrations/injectable-pipe",
|
||||
"//packages/core/schematics/migrations/missing-injectable",
|
||||
"//packages/core/schematics/migrations/move-document",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2/google3",
|
||||
"//packages/core/schematics/migrations/static-queries",
|
||||
"//packages/core/schematics/migrations/template-var-assignment",
|
||||
"//packages/core/schematics/utils",
|
||||
@ -26,7 +24,7 @@ ts_library(
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
name = "google3",
|
||||
deps = [
|
||||
":test_lib",
|
||||
"@npm//shelljs",
|
||||
|
20
packages/core/schematics/test/google3/BUILD.bazel
Normal file
20
packages/core/schematics/test/google3/BUILD.bazel
Normal file
@ -0,0 +1,20 @@
|
||||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = glob(["**/*.ts"]),
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/google3",
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//tslint",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
deps = [
|
||||
":test_lib",
|
||||
"@npm//shelljs",
|
||||
],
|
||||
)
|
@ -12,8 +12,8 @@ import * as shx from 'shelljs';
|
||||
import {Configuration, Linter} from 'tslint';
|
||||
|
||||
describe('Google3 Renderer to Renderer2 TSLint rule', () => {
|
||||
const rulesDirectory = dirname(
|
||||
require.resolve('../../migrations/renderer-to-renderer2/google3/rendererToRenderer2Rule'));
|
||||
const rulesDirectory =
|
||||
dirname(require.resolve('../../migrations/google3/rendererToRenderer2Rule'));
|
||||
|
||||
let tmpDir: string;
|
||||
|
||||
|
@ -95,9 +95,8 @@ export {
|
||||
ɵɵviewQuery,
|
||||
ɵɵstaticViewQuery,
|
||||
ɵɵstaticContentQuery,
|
||||
ɵɵloadViewQuery,
|
||||
ɵɵcontentQuery,
|
||||
ɵɵloadContentQuery,
|
||||
ɵɵloadQuery,
|
||||
ɵɵelementEnd,
|
||||
ɵɵhostProperty,
|
||||
ɵɵproperty,
|
||||
@ -153,7 +152,6 @@ export {
|
||||
ɵɵtemplate,
|
||||
ɵɵembeddedViewEnd,
|
||||
store as ɵstore,
|
||||
ɵɵload,
|
||||
ɵɵpipe,
|
||||
ɵɵBaseDef,
|
||||
ComponentDef as ɵComponentDef,
|
||||
|
@ -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;
|
||||
|
@ -140,7 +140,7 @@ export interface Directive {
|
||||
* class BankAccount {
|
||||
* bankName: string;
|
||||
* id: string;
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
namespaceHTMLInternal();
|
||||
const hostRNode = rootSelectorOrNode ?
|
||||
locateHostElement(rendererFactory, rootSelectorOrNode) :
|
||||
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef));
|
||||
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef), null);
|
||||
|
||||
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
||||
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
||||
@ -193,7 +193,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
component = createRootComponent(
|
||||
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
|
||||
|
||||
addToViewTree(rootLView, componentView);
|
||||
refreshDescendantViews(rootLView);
|
||||
safeToRunHooks = true;
|
||||
} finally {
|
||||
|
@ -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;
|
||||
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
|
||||
firstPassHooks :
|
||||
checkHooks;
|
||||
if (hooksToCall) {
|
||||
callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
|
||||
|
||||
if (checkHooks !== null || firstPassHooks !== null) {
|
||||
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
|
||||
firstPassHooks :
|
||||
checkHooks;
|
||||
if (hooksToCall !== null) {
|
||||
callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// The init phase state must be always checked here as it may have been recursively updated
|
||||
if (currentNodeIndex == null &&
|
||||
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState &&
|
||||
let flags = currentView[FLAGS];
|
||||
if (currentNodeIndex == null && (flags & LViewFlags.InitPhaseStateMask) === initPhaseState &&
|
||||
initPhaseState !== InitPhaseState.InitPhaseCompleted) {
|
||||
currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset;
|
||||
currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer;
|
||||
flags &= LViewFlags.IndexWithinInitPhaseReset;
|
||||
flags += LViewFlags.InitPhaseStateIncrementer;
|
||||
currentView[FLAGS] = flags;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
|
||||
import {addAllToArray} from '../util/array_utils';
|
||||
import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {bind, setDelayProjection, ɵɵload} from './instructions/all';
|
||||
import {bind, setDelayProjection} from './instructions/all';
|
||||
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
|
||||
import {TsickleIssue1009, allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, textBindingInternal} from './instructions/shared';
|
||||
import {LContainer, NATIVE} from './interfaces/container';
|
||||
@ -29,7 +29,7 @@ import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPrev
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {renderStringify} from './util/misc_utils';
|
||||
import {findComponentView} from './util/view_traversal_utils';
|
||||
import {getNativeByIndex, getNativeByTNode, getTNode} from './util/view_utils';
|
||||
import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils';
|
||||
|
||||
|
||||
const MARKER = `<EFBFBD>`;
|
||||
@ -912,7 +912,7 @@ function removeNode(index: number, viewData: LView) {
|
||||
nativeRemoveNode(viewData[RENDERER], removedPhRNode);
|
||||
}
|
||||
|
||||
const slotValue = ɵɵload(index) as RElement | RComment | LContainer;
|
||||
const slotValue = load(viewData, index) as RElement | RComment | LContainer;
|
||||
if (isLContainer(slotValue)) {
|
||||
const lContainer = slotValue as LContainer;
|
||||
if (removedPhTNode.type !== TNodeType.Container) {
|
||||
|
@ -72,7 +72,6 @@ export {
|
||||
ɵɵinjectAttribute,
|
||||
|
||||
ɵɵlistener,
|
||||
ɵɵload,
|
||||
|
||||
ɵɵnamespaceHTML,
|
||||
ɵɵnamespaceMathML,
|
||||
@ -178,9 +177,8 @@ export {
|
||||
ɵɵqueryRefresh,
|
||||
ɵɵviewQuery,
|
||||
ɵɵstaticViewQuery,
|
||||
ɵɵloadViewQuery,
|
||||
ɵɵloadQuery,
|
||||
ɵɵcontentQuery,
|
||||
ɵɵloadContentQuery,
|
||||
ɵɵstaticContentQuery
|
||||
} from './query';
|
||||
|
||||
|
@ -41,11 +41,11 @@ import {TsickleIssue1009, elementAttributeInternal} from './shared';
|
||||
export function ɵɵattributeInterpolate1(
|
||||
attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn,
|
||||
namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate1;
|
||||
}
|
||||
@ -79,11 +79,11 @@ export function ɵɵattributeInterpolate1(
|
||||
export function ɵɵattributeInterpolate2(
|
||||
attrName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string,
|
||||
sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate2;
|
||||
}
|
||||
@ -120,11 +120,11 @@ export function ɵɵattributeInterpolate2(
|
||||
export function ɵɵattributeInterpolate3(
|
||||
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any,
|
||||
suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate3;
|
||||
}
|
||||
@ -163,11 +163,11 @@ export function ɵɵattributeInterpolate3(
|
||||
export function ɵɵattributeInterpolate4(
|
||||
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate4;
|
||||
}
|
||||
@ -209,12 +209,12 @@ export function ɵɵattributeInterpolate5(
|
||||
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn,
|
||||
namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue =
|
||||
interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate5;
|
||||
}
|
||||
@ -258,12 +258,12 @@ export function ɵɵattributeInterpolate6(
|
||||
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn,
|
||||
namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue =
|
||||
interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate6;
|
||||
}
|
||||
@ -362,12 +362,12 @@ export function ɵɵattributeInterpolate8(
|
||||
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
|
||||
suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolatedValue = interpolation8(
|
||||
lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolate8;
|
||||
}
|
||||
@ -401,11 +401,11 @@ export function ɵɵattributeInterpolate8(
|
||||
export function ɵɵattributeInterpolateV(
|
||||
attrName: string, values: any[], sanitizer?: SanitizerFn,
|
||||
namespace?: string): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const interpolated = interpolationV(lView, values);
|
||||
if (interpolated !== NO_CHANGE) {
|
||||
elementAttributeInternal(index, attrName, interpolated, lView, sanitizer, namespace);
|
||||
elementAttributeInternal(
|
||||
getSelectedIndex(), attrName, interpolated, lView, sanitizer, namespace);
|
||||
}
|
||||
return ɵɵattributeInterpolateV;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../i
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild, removeView} from '../node_manipulation';
|
||||
import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {getNativeByTNode, loadInternal} from '../util/view_utils';
|
||||
import {getNativeByTNode, load} from '../util/view_utils';
|
||||
|
||||
import {addToViewTree, createDirectivesAndLocals, createLContainer, createTView, getOrCreateTNode, resolveDirectives} from './shared';
|
||||
|
||||
@ -76,7 +76,8 @@ export function ɵɵtemplate(
|
||||
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
|
||||
|
||||
const embeddedTView = tContainerNode.tViews = createTView(
|
||||
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
|
||||
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null,
|
||||
tView.schemas);
|
||||
if (tView.queries !== null) {
|
||||
tView.queries.template(tView, tContainerNode);
|
||||
embeddedTView.queries = tView.queries.embeddedTView(tContainerNode);
|
||||
@ -99,7 +100,7 @@ export function ɵɵtemplate(
|
||||
export function ɵɵcontainerRefreshStart(index: number): void {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
let previousOrParentTNode = loadInternal(tView.data, index) as TNode;
|
||||
let previousOrParentTNode = load(tView.data, index) as TNode;
|
||||
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
|
||||
setPreviousOrParentTNode(previousOrParentTNode, true);
|
||||
|
||||
|
@ -16,7 +16,7 @@ import {isContentQueryHost} from '../interfaces/type_checks';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild} from '../node_manipulation';
|
||||
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
|
||||
import {StylingMapArray, TStylingContext} from '../styling_next/interfaces';
|
||||
import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_next/util';
|
||||
@ -52,13 +52,13 @@ export function ɵɵelementStart(
|
||||
|
||||
ngDevMode && ngDevMode.rendererCreateElement++;
|
||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
||||
const native = lView[index + HEADER_OFFSET] = elementCreate(name);
|
||||
const renderer = lView[RENDERER];
|
||||
const native = lView[index + HEADER_OFFSET] = elementCreate(name, renderer, getNamespace());
|
||||
const tNode =
|
||||
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs || null);
|
||||
|
||||
if (attrs != null) {
|
||||
const lastAttrIndex = setUpAttributes(native, attrs);
|
||||
const lastAttrIndex = setUpAttributes(renderer, native, attrs);
|
||||
if (tView.firstTemplatePass) {
|
||||
registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
||||
}
|
||||
@ -209,7 +209,7 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
|
||||
// errors...
|
||||
if (tNode.type === TNodeType.Element) {
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const lastAttrIndex = setUpAttributes(native, attrs);
|
||||
const lastAttrIndex = setUpAttributes(lView[RENDERER], native, attrs);
|
||||
if (tView.firstTemplatePass) {
|
||||
const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
||||
|
||||
|
@ -11,11 +11,11 @@ import {assertLContainerOrUndefined} from '../assert';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||
import {RenderFlags} from '../interfaces/definition';
|
||||
import {TContainerNode, TNodeType} from '../interfaces/node';
|
||||
import {FLAGS, LView, LViewFlags, PARENT, QUERIES, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {FLAGS, LView, LViewFlags, PARENT, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {insertView, removeView} from '../node_manipulation';
|
||||
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
|
||||
import {resetPreOrderHookFlags} from '../util/view_utils';
|
||||
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
|
||||
import {isCreationMode, resetPreOrderHookFlags} from '../util/view_utils';
|
||||
import {assignTViewNodeToLView, createLView, createTView, refreshDescendantViews} from './shared';
|
||||
|
||||
/**
|
||||
|
@ -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 =
|
||||
|
@ -81,10 +81,9 @@ export function ɵɵpropertyInterpolate(
|
||||
export function ɵɵpropertyInterpolate1(
|
||||
propName: string, prefix: string, v0: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue = interpolation1(getLView(), prefix, v0, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate1;
|
||||
}
|
||||
@ -122,10 +121,9 @@ export function ɵɵpropertyInterpolate1(
|
||||
export function ɵɵpropertyInterpolate2(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue = interpolation2(getLView(), prefix, v0, i0, v1, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate2;
|
||||
}
|
||||
@ -166,10 +164,9 @@ export function ɵɵpropertyInterpolate2(
|
||||
export function ɵɵpropertyInterpolate3(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any,
|
||||
suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue = interpolation3(getLView(), prefix, v0, i0, v1, i1, v2, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate3;
|
||||
}
|
||||
@ -212,10 +209,9 @@ export function ɵɵpropertyInterpolate3(
|
||||
export function ɵɵpropertyInterpolate4(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue = interpolation4(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate4;
|
||||
}
|
||||
@ -260,11 +256,10 @@ export function ɵɵpropertyInterpolate4(
|
||||
export function ɵɵpropertyInterpolate5(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue =
|
||||
interpolation5(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate5;
|
||||
}
|
||||
@ -312,11 +307,10 @@ export function ɵɵpropertyInterpolate6(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue =
|
||||
interpolation6(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate6;
|
||||
}
|
||||
@ -366,11 +360,10 @@ export function ɵɵpropertyInterpolate7(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue = interpolation7(
|
||||
getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate7;
|
||||
}
|
||||
@ -422,11 +415,10 @@ export function ɵɵpropertyInterpolate8(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
|
||||
suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
const interpolatedValue = interpolation8(
|
||||
getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolate8;
|
||||
}
|
||||
@ -463,11 +455,9 @@ export function ɵɵpropertyInterpolate8(
|
||||
*/
|
||||
export function ɵɵpropertyInterpolateV(
|
||||
propName: string, values: any[], sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
|
||||
const interpolatedValue = interpolationV(getLView(), values);
|
||||
if (interpolatedValue !== NO_CHANGE) {
|
||||
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
|
||||
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
|
||||
}
|
||||
return ɵɵpropertyInterpolateV;
|
||||
}
|
||||
|
@ -7,14 +7,13 @@
|
||||
*/
|
||||
import {Injector} from '../../di';
|
||||
import {ErrorHandler} from '../../error_handler';
|
||||
import {Type} from '../../interface/type';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
|
||||
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
|
||||
import {Sanitizer} from '../../sanitization/security';
|
||||
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert';
|
||||
import {createNamedArrayType} from '../../util/named_array_type';
|
||||
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
|
||||
import {assertLView, assertPreviousIsParent} from '../assert';
|
||||
import {assertFirstTemplatePass, assertLView} from '../assert';
|
||||
import {attachPatchData, getComponentViewByInstance} from '../context_discovery';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
|
||||
import {throwMultipleComponentError} from '../errors';
|
||||
@ -26,16 +25,16 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
|
||||
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {renderStylingMap} from '../styling_next/bindings';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
|
||||
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
||||
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
import {getLViewParent} from '../util/view_traversal_utils';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
|
||||
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
|
||||
import {selectInternal} from './select';
|
||||
@ -63,15 +62,11 @@ export function refreshDescendantViews(lView: LView) {
|
||||
const tView = lView[TVIEW];
|
||||
const creationMode = isCreationMode(lView);
|
||||
|
||||
// This needs to be set before children are processed to support recursive components
|
||||
tView.firstTemplatePass = false;
|
||||
|
||||
// Resetting the bindingIndex of the current LView as the next steps may trigger change detection.
|
||||
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
||||
|
||||
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
|
||||
// This will be done in the update pass.
|
||||
if (!creationMode) {
|
||||
// Resetting the bindingIndex of the current LView as the next steps may trigger change
|
||||
// detection.
|
||||
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
||||
|
||||
const checkNoChangesMode = getCheckNoChangesMode();
|
||||
|
||||
executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
|
||||
@ -79,7 +74,9 @@ export function refreshDescendantViews(lView: LView) {
|
||||
refreshDynamicEmbeddedViews(lView);
|
||||
|
||||
// Content query results must be refreshed before content hooks are called.
|
||||
refreshContentQueries(tView, lView);
|
||||
if (tView.contentQueries !== null) {
|
||||
refreshContentQueries(tView, lView);
|
||||
}
|
||||
|
||||
resetPreOrderHookFlags(lView);
|
||||
executeHooks(
|
||||
@ -87,14 +84,22 @@ export function refreshDescendantViews(lView: LView) {
|
||||
InitPhaseState.AfterContentInitHooksToBeRun, undefined);
|
||||
|
||||
setHostBindings(tView, lView);
|
||||
} else {
|
||||
// This needs to be set before children are processed to support recursive components.
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
// ngFor loop, all the views will be created together before update mode runs and turns
|
||||
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
||||
// matching, etc again and again.
|
||||
tView.firstTemplatePass = false;
|
||||
|
||||
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
|
||||
// content queries are resolved during change detection (i.e. update mode), after embedded
|
||||
// views are refreshed (see block above).
|
||||
if (tView.staticContentQueries) {
|
||||
refreshContentQueries(tView, lView);
|
||||
}
|
||||
}
|
||||
|
||||
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
|
||||
// content queries are resolved during change detection (i.e. update mode), after embedded
|
||||
// views are refreshed (see block above).
|
||||
if (creationMode && tView.staticContentQueries) {
|
||||
refreshContentQueries(tView, lView);
|
||||
}
|
||||
|
||||
// We must materialize query results before child components are processed
|
||||
// in case a child component has projected a container. The LContainer needs
|
||||
@ -103,7 +108,10 @@ export function refreshDescendantViews(lView: LView) {
|
||||
executeViewQueryFn(RenderFlags.Update, tView, lView[CONTEXT]);
|
||||
}
|
||||
|
||||
refreshChildComponents(lView, tView.components);
|
||||
const components = tView.components;
|
||||
if (components !== null) {
|
||||
refreshChildComponents(lView, components);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -111,7 +119,7 @@ export function refreshDescendantViews(lView: LView) {
|
||||
export function setHostBindings(tView: TView, viewData: LView): void {
|
||||
const selectedIndex = getSelectedIndex();
|
||||
try {
|
||||
if (tView.expandoInstructions) {
|
||||
if (tView.expandoInstructions !== null) {
|
||||
let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex;
|
||||
setBindingRoot(bindingRootIndex);
|
||||
let currentDirectiveIndex = -1;
|
||||
@ -179,11 +187,9 @@ function refreshContentQueries(tView: TView, lView: LView): void {
|
||||
}
|
||||
|
||||
/** Refreshes child components in the current view. */
|
||||
function refreshChildComponents(hostLView: LView, components: number[] | null): void {
|
||||
if (components != null) {
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
componentRefresh(hostLView, components[i]);
|
||||
}
|
||||
function refreshChildComponents(hostLView: LView, components: number[]): void {
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
componentRefresh(hostLView, components[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,26 +197,19 @@ function refreshChildComponents(hostLView: LView, components: number[] | null):
|
||||
/**
|
||||
* Creates a native element from a tag name, using a renderer.
|
||||
* @param name the tag name
|
||||
* @param overriddenRenderer Optional A renderer to override the default one
|
||||
* @param renderer A renderer to use
|
||||
* @returns the element created
|
||||
*/
|
||||
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
|
||||
let native: RElement;
|
||||
const rendererToUse = overriddenRenderer || getLView()[RENDERER];
|
||||
|
||||
const namespace = getNamespace();
|
||||
|
||||
if (isProceduralRenderer(rendererToUse)) {
|
||||
native = rendererToUse.createElement(name, namespace);
|
||||
export function elementCreate(
|
||||
name: string, renderer: Renderer3, namespace: string | null): RElement {
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
return renderer.createElement(name, namespace);
|
||||
} else {
|
||||
if (namespace === null) {
|
||||
native = rendererToUse.createElement(name);
|
||||
} else {
|
||||
native = rendererToUse.createElementNS(namespace, name);
|
||||
}
|
||||
return namespace === null ? renderer.createElement(name) :
|
||||
renderer.createElementNS(namespace, name);
|
||||
}
|
||||
return native;
|
||||
}
|
||||
|
||||
export function createLView<T>(
|
||||
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags,
|
||||
host: RElement | null, tHostNode: TViewNode | TElementNode | null,
|
||||
@ -363,16 +362,11 @@ export function allocExpando(view: LView, numSlotsToAlloc: number) {
|
||||
//////////////////////////
|
||||
|
||||
/**
|
||||
* Used for creating the LViewNode of a dynamic embedded view,
|
||||
* either through ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
|
||||
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
||||
* Used for creating the LView of a dynamic embedded view, either through
|
||||
* ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
|
||||
*/
|
||||
export function createEmbeddedViewAndNode<T>(
|
||||
tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
|
||||
const _isParent = getIsParent();
|
||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
||||
setPreviousOrParentTNode(null !, true);
|
||||
|
||||
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
|
||||
lView[DECLARATION_VIEW] = declarationView;
|
||||
|
||||
@ -382,12 +376,12 @@ export function createEmbeddedViewAndNode<T>(
|
||||
tView.node !.injectorIndex = injectorIndex;
|
||||
}
|
||||
|
||||
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
|
||||
return lView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for rendering embedded views (e.g. dynamically created views)
|
||||
* Used for rendering views in a LContainer (embedded views or root component views for dynamically
|
||||
* created components).
|
||||
*
|
||||
* Dynamically created views must store/retrieve their TViews differently from component views
|
||||
* because their template functions are nested in the template functions of their hosts, creating
|
||||
@ -401,31 +395,20 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
|
||||
const _isParent = getIsParent();
|
||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
||||
let oldView: LView;
|
||||
if (viewToRender[FLAGS] & LViewFlags.IsRoot) {
|
||||
// This is a root view inside the view tree
|
||||
tickRootContext(getRootContext(viewToRender));
|
||||
} else {
|
||||
// Will become true if the `try` block executes with no errors.
|
||||
let safeToRunHooks = false;
|
||||
try {
|
||||
setPreviousOrParentTNode(null !, true);
|
||||
|
||||
oldView = enterView(viewToRender, viewToRender[T_HOST]);
|
||||
resetPreOrderHookFlags(viewToRender);
|
||||
executeTemplate(viewToRender, tView.template !, getRenderFlags(viewToRender), context);
|
||||
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
// ngFor loop, all the views will be created together before update mode runs and turns
|
||||
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
||||
// matching, etc again and again.
|
||||
tView.firstTemplatePass = false;
|
||||
|
||||
refreshDescendantViews(viewToRender);
|
||||
safeToRunHooks = true;
|
||||
} finally {
|
||||
leaveView(oldView !, safeToRunHooks);
|
||||
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
|
||||
// Will become true if the `try` block executes with no errors.
|
||||
let safeToRunHooks = false;
|
||||
try {
|
||||
oldView = enterView(viewToRender, viewToRender[T_HOST]);
|
||||
resetPreOrderHookFlags(viewToRender);
|
||||
const templateFn = tView.template;
|
||||
if (templateFn !== null) {
|
||||
executeTemplate(viewToRender, templateFn, getRenderFlags(viewToRender), context);
|
||||
}
|
||||
refreshDescendantViews(viewToRender);
|
||||
safeToRunHooks = true;
|
||||
} finally {
|
||||
leaveView(oldView !, safeToRunHooks);
|
||||
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -644,11 +627,14 @@ export function createTView(
|
||||
}
|
||||
|
||||
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
|
||||
const blueprint = new (ngDevMode ? LViewBlueprint ! : Array)(initialViewLength)
|
||||
.fill(null, 0, bindingStartIndex)
|
||||
.fill(NO_CHANGE, bindingStartIndex) as LView;
|
||||
const blueprint = ngDevMode ? new LViewBlueprint !() : [];
|
||||
|
||||
for (let i = 0; i < initialViewLength; i++) {
|
||||
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
|
||||
}
|
||||
blueprint[BINDING_INDEX] = bindingStartIndex;
|
||||
return blueprint;
|
||||
|
||||
return blueprint as LView;
|
||||
}
|
||||
|
||||
export function createError(text: string, token: any) {
|
||||
@ -1029,7 +1015,7 @@ export function resolveDirectives(
|
||||
localRefs: string[] | null): void {
|
||||
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
|
||||
// tsickle.
|
||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
|
||||
if (!getBindingsEnabled()) return;
|
||||
|
||||
@ -1061,6 +1047,10 @@ export function resolveDirectives(
|
||||
|
||||
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
|
||||
|
||||
if (def.contentQueries) {
|
||||
tNode.flags |= TNodeFlags.hasContentQuery;
|
||||
}
|
||||
|
||||
// Init hooks are queued now so ngOnInit is called in host components before
|
||||
// any projected components.
|
||||
registerPreOrderHooks(
|
||||
@ -1087,7 +1077,7 @@ function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
|
||||
addComponentLogic(lView, tNode, def as ComponentDef<any>);
|
||||
}
|
||||
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
|
||||
postProcessDirective(lView, directive, def, i);
|
||||
postProcessDirective(lView, tNode, directive, def, i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1162,20 +1152,15 @@ export function generateExpandoInstructionBlock(
|
||||
* Process a directive on the current node after its creation.
|
||||
*/
|
||||
function postProcessDirective<T>(
|
||||
viewData: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
postProcessBaseDirective(viewData, previousOrParentTNode, directive);
|
||||
ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
|
||||
if (previousOrParentTNode && previousOrParentTNode.attrs) {
|
||||
setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
|
||||
}
|
||||
|
||||
if (viewData[TVIEW].firstTemplatePass && def.contentQueries) {
|
||||
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
|
||||
lView: LView, hostTNode: TNode, directive: T, def: DirectiveDef<T>,
|
||||
directiveDefIdx: number): void {
|
||||
postProcessBaseDirective(lView, hostTNode, directive);
|
||||
if (hostTNode.attrs !== null) {
|
||||
setInputsFromAttrs(directiveDefIdx, directive, def, hostTNode);
|
||||
}
|
||||
|
||||
if (isComponentDef(def)) {
|
||||
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
|
||||
const componentView = getComponentViewByIndex(hostTNode.index, lView);
|
||||
componentView[CONTEXT] = directive;
|
||||
}
|
||||
}
|
||||
@ -1183,16 +1168,12 @@ function postProcessDirective<T>(
|
||||
/**
|
||||
* A lighter version of postProcessDirective() that is used for the root component.
|
||||
*/
|
||||
function postProcessBaseDirective<T>(
|
||||
lView: LView, previousOrParentTNode: TNode, directive: T): void {
|
||||
const native = getNativeByTNode(previousOrParentTNode, lView);
|
||||
|
||||
function postProcessBaseDirective<T>(lView: LView, hostTNode: TNode, directive: T): void {
|
||||
ngDevMode && assertEqual(
|
||||
lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex,
|
||||
'directives should be created before any bindings');
|
||||
ngDevMode && assertPreviousIsParent(getIsParent());
|
||||
|
||||
attachPatchData(directive, lView);
|
||||
const native = getNativeByTNode(hostTNode, lView);
|
||||
if (native) {
|
||||
attachPatchData(native, lView);
|
||||
}
|
||||
@ -1206,7 +1187,9 @@ function postProcessBaseDirective<T>(
|
||||
function findDirectiveMatches(
|
||||
tView: TView, viewData: LView,
|
||||
tNode: TElementNode | TContainerNode | TElementContainerNode): DirectiveDef<any>[]|null {
|
||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
ngDevMode && assertNodeOfPossibleTypes(
|
||||
tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container);
|
||||
const registry = tView.directiveRegistry;
|
||||
let matches: any[]|null = null;
|
||||
if (registry) {
|
||||
@ -1218,8 +1201,7 @@ function findDirectiveMatches(
|
||||
|
||||
if (isComponentDef(def)) {
|
||||
if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
|
||||
tNode.flags = TNodeFlags.isComponent;
|
||||
|
||||
markAsComponentHost(tView, tNode);
|
||||
// The component is always stored first with directives after.
|
||||
matches.unshift(def);
|
||||
} else {
|
||||
@ -1231,13 +1213,16 @@ function findDirectiveMatches(
|
||||
return matches;
|
||||
}
|
||||
|
||||
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
||||
export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode &&
|
||||
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
|
||||
/**
|
||||
* Marks a given TNode as a component's host. This consists of:
|
||||
* - setting appropriate TNode flags;
|
||||
* - storing index of component's host element so it will be queued for view refresh during CD.
|
||||
*/
|
||||
export function markAsComponentHost(tView: TView, hostTNode: TNode): void {
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
hostTNode.flags = TNodeFlags.isComponent;
|
||||
(tView.components || (tView.components = ngDevMode ? new TViewComponents !() : [
|
||||
])).push(previousOrParentTNode.index);
|
||||
])).push(hostTNode.index);
|
||||
}
|
||||
|
||||
|
||||
@ -1305,10 +1290,8 @@ function baseResolveDirective<T>(
|
||||
viewData.push(nodeInjectorFactory);
|
||||
}
|
||||
|
||||
function addComponentLogic<T>(
|
||||
lView: LView, previousOrParentTNode: TNode, def: ComponentDef<T>): void {
|
||||
const native = getNativeByTNode(previousOrParentTNode, lView);
|
||||
|
||||
function addComponentLogic<T>(lView: LView, hostTNode: TNode, def: ComponentDef<T>): void {
|
||||
const native = getNativeByTNode(hostTNode, lView);
|
||||
const tView = getOrCreateTView(def);
|
||||
|
||||
// Only component views should be added to the view tree directly. Embedded views are
|
||||
@ -1317,18 +1300,14 @@ function addComponentLogic<T>(
|
||||
const componentView = addToViewTree(
|
||||
lView, createLView(
|
||||
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
||||
lView[previousOrParentTNode.index], previousOrParentTNode as TElementNode,
|
||||
rendererFactory, rendererFactory.createRenderer(native as RElement, def)));
|
||||
lView[hostTNode.index], hostTNode as TElementNode, rendererFactory,
|
||||
rendererFactory.createRenderer(native as RElement, def)));
|
||||
|
||||
componentView[T_HOST] = previousOrParentTNode as TElementNode;
|
||||
componentView[T_HOST] = hostTNode as TElementNode;
|
||||
|
||||
// Component view will always be created before any injected LContainers,
|
||||
// so this is a regular element, wrap it with the component view
|
||||
lView[previousOrParentTNode.index] = componentView;
|
||||
|
||||
if (lView[TVIEW].firstTemplatePass) {
|
||||
queueComponentIndexForCheck(previousOrParentTNode);
|
||||
}
|
||||
lView[hostTNode.index] = componentView;
|
||||
}
|
||||
|
||||
export function elementAttributeInternal(
|
||||
@ -1494,19 +1473,20 @@ export function createLContainer(
|
||||
* by executing an associated template function.
|
||||
*/
|
||||
function refreshDynamicEmbeddedViews(lView: LView) {
|
||||
for (let current = lView[CHILD_HEAD]; current !== null; current = current[NEXT]) {
|
||||
// Note: current can be an LView or an LContainer instance, but here we are only interested
|
||||
// in LContainer. We can tell it's an LContainer because its length is less than the LView
|
||||
// header.
|
||||
if (current[ACTIVE_INDEX] === -1 && isLContainer(current)) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < current.length; i++) {
|
||||
const dynamicViewData = current[i];
|
||||
let viewOrContainer = lView[CHILD_HEAD];
|
||||
while (viewOrContainer !== null) {
|
||||
// Note: viewOrContainer can be an LView or an LContainer instance, but here we are only
|
||||
// interested in LContainer
|
||||
if (isLContainer(viewOrContainer) && viewOrContainer[ACTIVE_INDEX] === -1) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < viewOrContainer.length; i++) {
|
||||
const embeddedLView = viewOrContainer[i];
|
||||
// The directives and pipes are not needed here as an existing view is only being
|
||||
// refreshed.
|
||||
ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated');
|
||||
renderEmbeddedTemplate(dynamicViewData, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !);
|
||||
ngDevMode && assertDefined(embeddedLView[TVIEW], 'TView must be allocated');
|
||||
renderEmbeddedTemplate(embeddedLView, embeddedLView[TVIEW], embeddedLView[CONTEXT] !);
|
||||
}
|
||||
}
|
||||
viewOrContainer = viewOrContainer[NEXT];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1563,7 +1543,7 @@ export function componentRefresh(hostLView: LView, adjustedElementIndex: number)
|
||||
function syncViewWithBlueprint(componentView: LView) {
|
||||
const componentTView = componentView[TVIEW];
|
||||
for (let i = componentView.length; i < componentTView.blueprint.length; i++) {
|
||||
componentView[i] = componentTView.blueprint[i];
|
||||
componentView.push(componentTView.blueprint[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user