Compare commits
69 Commits
zone.js-0.
...
10.1.1
Author | SHA1 | Date | |
---|---|---|---|
1c156eb304 | |||
396548442e | |||
c54161098d | |||
f1b355b54f | |||
e40ffb95c8 | |||
20564f997f | |||
b1398d1771 | |||
7e9134aae8 | |||
0a55058440 | |||
bb1122d087 | |||
2ea49c7add | |||
83d69978fd | |||
62de2131e1 | |||
e156e29edd | |||
775c305771 | |||
190dca0fdc | |||
309709d4b2 | |||
028ef30b34 | |||
56d5ff2a89 | |||
b4eb016e56 | |||
6b0dba48b1 | |||
cfd4c0b4dc | |||
38762020d3 | |||
a1c34c6f0a | |||
b084bffb64 | |||
6a28675a5e | |||
4de8dc3554 | |||
ab4f953c78 | |||
ee432aaab8 | |||
5863537575 | |||
fcd2eb2ffb | |||
251a28cb15 | |||
54bb1c3d6a | |||
6c6dd5f38c | |||
9794f20674 | |||
027b041cfd | |||
4886cf5965 | |||
f21d50d2e6 | |||
0ef985368e | |||
0a277c6c40 | |||
9bf32c4dcb | |||
1c2ccfed4d | |||
25afbcc459 | |||
29c89c9297 | |||
efc76064d9 | |||
dbab74429f | |||
6aac499ee7 | |||
32f33f095f | |||
b0bd777ba9 | |||
c01bd0fe8e | |||
5588324802 | |||
437ecc8090 | |||
0dda97ea66 | |||
5e4aeaa348 | |||
cbbf8b542f | |||
91dfb18840 | |||
e44ddf5baa | |||
6b1a505566 | |||
659705ad78 | |||
8864b0ed69 | |||
4e596b672f | |||
83866827c3 | |||
7006cac50a | |||
dd82f2fefd | |||
bf003340ab | |||
5e35edd724 | |||
c132dcd0ae | |||
bbe331569b | |||
21e9a0032c |
28
.github/angular-robot.yml
vendored
28
.github/angular-robot.yml
vendored
@ -73,15 +73,14 @@ merge:
|
||||
- "packages/zone.js/scripts/**"
|
||||
|
||||
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable
|
||||
mergeConflictComment: "Hi @{{PRAuthor}}! This PR has merge conflicts due to recent upstream merges.
|
||||
\nPlease help to unblock it by resolving these conflicts. Thanks!"
|
||||
mergeConflictComment: "Hi @{{PRAuthor}}! This PR has merge conflicts due to recent upstream merges.\nPlease help to unblock it by resolving these conflicts. Thanks!"
|
||||
|
||||
# label to monitor
|
||||
mergeLabel: "PR action: merge"
|
||||
mergeLabel: "action: merge"
|
||||
|
||||
# adding any of these labels will also add the merge label
|
||||
mergeLinkedLabels:
|
||||
- "PR action: merge-assistance"
|
||||
- "action: merge-assistance"
|
||||
|
||||
# list of checks that will determine if the merge label can be added
|
||||
checks:
|
||||
@ -94,17 +93,17 @@ merge:
|
||||
|
||||
# whether the PR shouldn't have a conflict with the base branch
|
||||
noConflict: true
|
||||
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")
|
||||
# list of labels that a PR needs to have, checked with a regexp (e.g. "target:" will work for the label "target: master")
|
||||
requiredLabels:
|
||||
- "PR target: *"
|
||||
- "target: *"
|
||||
- "cla: yes"
|
||||
|
||||
# list of labels that a PR shouldn't have, checked after the required labels with a regexp
|
||||
forbiddenLabels:
|
||||
- "PR target: TBD"
|
||||
- "PR action: cleanup"
|
||||
- "PR action: review"
|
||||
- "PR state: blocked"
|
||||
- "target: TBD"
|
||||
- "action: cleanup"
|
||||
- "action: review"
|
||||
- "state: blocked"
|
||||
- "cla: no"
|
||||
|
||||
# list of PR statuses that need to be successful
|
||||
@ -121,12 +120,7 @@ merge:
|
||||
# the comment that will be added when the merge label is added despite failing checks, leave empty or set to false to disable
|
||||
# {{MERGE_LABEL}} will be replaced by the value of the mergeLabel option
|
||||
# {{PLACEHOLDER}} will be replaced by the list of failing checks
|
||||
mergeRemovedComment: "I see that you just added the `{{MERGE_LABEL}}` label, but the following checks are still failing:
|
||||
\n{{PLACEHOLDER}}
|
||||
\n
|
||||
\n**If you want your PR to be merged, it has to pass all the CI checks.**
|
||||
\n
|
||||
\nIf you can't get the PR to a green state due to flakes or broken master, please try rebasing to master and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help."
|
||||
mergeRemovedComment: "I see that you just added the `{{MERGE_LABEL}}` label, but the following checks are still failing:\n{{PLACEHOLDER}}\n\n**If you want your PR to be merged, it has to pass all the CI checks.**\n\nIf you can't get the PR to a green state due to flakes or broken master, please try rebasing to master and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help."
|
||||
|
||||
# options for the triage plugin
|
||||
triage:
|
||||
@ -186,4 +180,4 @@ rerunCircleCI:
|
||||
# set to true to disable
|
||||
disabled: false
|
||||
# the label which when added triggers a rerun of the default CircleCI workflow
|
||||
triggerRerunLabel: "PR action: rerun CI at HEAD"
|
||||
triggerRerunLabel: "action: rerun CI at HEAD"
|
||||
|
19
.ng-dev/caretaker.ts
Normal file
19
.ng-dev/caretaker.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {CaretakerConfig} from '../dev-infra/caretaker/config';
|
||||
|
||||
/** The configuration for `ng-dev caretaker` commands. */
|
||||
export const caretaker: CaretakerConfig = {
|
||||
githubQueries: [
|
||||
{
|
||||
name: 'Merge Queue',
|
||||
query: `is:pr is:open status:success label:"action: merge"`,
|
||||
},
|
||||
{
|
||||
name: 'Merge Assistance Queue',
|
||||
query: `is:pr is:open status:success label:"action: merge-assistance"`,
|
||||
},
|
||||
{
|
||||
name: 'Primary Triage Queue',
|
||||
query: `is:open is:issue no:milestone`,
|
||||
}
|
||||
]
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import {caretaker} from './caretaker';
|
||||
import {commitMessage} from './commit-message';
|
||||
import {format} from './format';
|
||||
import {github} from './github';
|
||||
@ -8,4 +9,5 @@ module.exports = {
|
||||
format,
|
||||
github,
|
||||
merge,
|
||||
caretaker,
|
||||
};
|
||||
|
@ -1,38 +1,25 @@
|
||||
import {MergeConfig} from '../dev-infra/pr/merge/config';
|
||||
import {DevInfraMergeConfig} from '../dev-infra/pr/merge/config';
|
||||
import {getDefaultTargetLabelConfiguration} from '../dev-infra/pr/merge/defaults';
|
||||
import {github} from './github';
|
||||
|
||||
/**
|
||||
* Configuration for the merge tool in `ng-dev`. This sets up the labels which
|
||||
* are respected by the merge script (e.g. the target labels).
|
||||
*/
|
||||
export const merge = (): MergeConfig => {
|
||||
// TODO: resume dynamically determining patch branch
|
||||
const patch = '10.0.x';
|
||||
export const merge: DevInfraMergeConfig['merge'] = async api => {
|
||||
return {
|
||||
githubApiMerge: false,
|
||||
claSignedLabel: 'cla: yes',
|
||||
mergeReadyLabel: /^PR action: merge(-assistance)?/,
|
||||
caretakerNoteLabel: 'PR action: merge-assistance',
|
||||
mergeReadyLabel: /^action: merge(-assistance)?/,
|
||||
caretakerNoteLabel: 'action: merge-assistance',
|
||||
commitMessageFixupLabel: 'commit message fixup',
|
||||
labels: [
|
||||
{
|
||||
pattern: 'PR target: master-only',
|
||||
branches: ['master'],
|
||||
},
|
||||
{
|
||||
pattern: 'PR target: patch-only',
|
||||
branches: [patch],
|
||||
},
|
||||
{
|
||||
pattern: 'PR target: master & patch',
|
||||
branches: ['master', patch],
|
||||
},
|
||||
],
|
||||
labels: await getDefaultTargetLabelConfiguration(api, github, '@angular/core'),
|
||||
requiredBaseCommits: {
|
||||
// PRs that target either `master` or the patch branch, need to be rebased
|
||||
// on top of the latest commit message validation fix.
|
||||
// These SHAs are the commits that update the required license text in the header.
|
||||
'master': '5aeb9a4124922d8ac08eb73b8f322905a32b0b3a',
|
||||
[patch]: '27b95ba64a5d99757f4042073fd1860e20e3ed24'
|
||||
'10.0.x': '27b95ba64a5d99757f4042073fd1860e20e3ed24',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
276
CHANGELOG.md
276
CHANGELOG.md
@ -1,11 +1,98 @@
|
||||
<a name="10.1.0-rc.0"></a>
|
||||
# 10.1.0-rc.0 (2020-08-26)
|
||||
<a name="10.1.1"></a>
|
||||
## 10.1.1 (2020-09-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** correct confusion between field and property names ([#38685](https://github.com/angular/angular/issues/38685)) ([a1c34c6](https://github.com/angular/angular/commit/a1c34c6))
|
||||
* **compiler-cli:** compute source-mappings for localized strings ([#38747](https://github.com/angular/angular/issues/38747)) ([b4eb016](https://github.com/angular/angular/commit/b4eb016)), closes [#38588](https://github.com/angular/angular/issues/38588)
|
||||
* **compiler-cli:** ensure that a declaration is available in type-to-value conversion ([#38684](https://github.com/angular/angular/issues/38684)) ([56d5ff2](https://github.com/angular/angular/commit/56d5ff2)), closes [#38670](https://github.com/angular/angular/issues/38670)
|
||||
* **core:** reset `tView` between tests in Ivy TestBed ([#38659](https://github.com/angular/angular/issues/38659)) ([efc7606](https://github.com/angular/angular/commit/efc7606)), closes [#38600](https://github.com/angular/angular/issues/38600)
|
||||
* **localize:** do not expose NodeJS typings in $localize runtime code ([#38700](https://github.com/angular/angular/issues/38700)) ([4de8dc3](https://github.com/angular/angular/commit/4de8dc3)), closes [#38692](https://github.com/angular/angular/issues/38692)
|
||||
* **localize:** enable whitespace preservation marker in XLIFF files ([#38737](https://github.com/angular/angular/issues/38737)) ([190dca0](https://github.com/angular/angular/commit/190dca0)), closes [#38679](https://github.com/angular/angular/issues/38679)
|
||||
* **localize:** install `[@angular](https://github.com/angular)/localize` in `devDependencies` by default ([#38680](https://github.com/angular/angular/issues/38680)) ([dbab744](https://github.com/angular/angular/commit/dbab744)), closes [#38329](https://github.com/angular/angular/issues/38329)
|
||||
* **localize:** render context of translation file parse errors ([#38673](https://github.com/angular/angular/issues/38673)) ([32f33f0](https://github.com/angular/angular/commit/32f33f0)), closes [#38377](https://github.com/angular/angular/issues/38377)
|
||||
* **localize:** render location in XLIFF 2 even if there is no metadata ([#38713](https://github.com/angular/angular/issues/38713)) ([ab4f953](https://github.com/angular/angular/commit/ab4f953)), closes [#38705](https://github.com/angular/angular/issues/38705)
|
||||
* **ngcc:** use aliased exported types correctly ([#38666](https://github.com/angular/angular/issues/38666)) ([6a28675](https://github.com/angular/angular/commit/6a28675)), closes [#38238](https://github.com/angular/angular/issues/38238)
|
||||
* **router:** If users are using the Alt key when clicking the router links, prioritize browser’s default behavior ([#38375](https://github.com/angular/angular/issues/38375)) ([309709d](https://github.com/angular/angular/commit/309709d))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **core:** use `ngDevMode` to tree-shake error messages ([#38612](https://github.com/angular/angular/issues/38612)) ([b084bff](https://github.com/angular/angular/commit/b084bff))
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0"></a>
|
||||
# 10.1.0 (2020-09-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** provide LinkablePackageInfo from ng_module ([#37623](https://github.com/angular/angular/issues/37623)) ([6898eab](https://github.com/angular/angular/commit/6898eab))
|
||||
* **common:** add ReadonlyMap in place of Map in keyValuePipe ([#37311](https://github.com/angular/angular/issues/37311)) ([3373453](https://github.com/angular/angular/commit/3373453)), closes [#37308](https://github.com/angular/angular/issues/37308)
|
||||
* **compiler-cli:** add `SourceFile.getOriginalLocation()` to sourcemaps package ([#32912](https://github.com/angular/angular/issues/32912)) ([6abb8d0](https://github.com/angular/angular/commit/6abb8d0))
|
||||
* **compiler-cli:** Add compiler option to report errors when assigning to restricted input fields ([#38249](https://github.com/angular/angular/issues/38249)) ([71138f6](https://github.com/angular/angular/commit/71138f6))
|
||||
* **compiler-cli:** add support for TypeScript 4.0 ([#38076](https://github.com/angular/angular/issues/38076)) ([0fc44e0](https://github.com/angular/angular/commit/0fc44e0))
|
||||
* **compiler-cli:** explain why an expression cannot be used in AOT compilations ([#37587](https://github.com/angular/angular/issues/37587)) ([712f1bd](https://github.com/angular/angular/commit/712f1bd))
|
||||
* **compiler:** support unary operators for more accurate type checking ([#37918](https://github.com/angular/angular/issues/37918)) ([874792d](https://github.com/angular/angular/commit/874792d)), closes [#20845](https://github.com/angular/angular/issues/20845) [#36178](https://github.com/angular/angular/issues/36178)
|
||||
* **core:** rename async to waitForAsync to avoid confusing ([#37583](https://github.com/angular/angular/issues/37583)) ([8f07429](https://github.com/angular/angular/commit/8f07429))
|
||||
* **core:** support injection token as predicate in queries ([#37506](https://github.com/angular/angular/issues/37506)) ([97dc85b](https://github.com/angular/angular/commit/97dc85b)), closes [#21152](https://github.com/angular/angular/issues/21152) [#36144](https://github.com/angular/angular/issues/36144)
|
||||
* **core:** update reference and doc to change `async` to `waitAsync`. ([#37583](https://github.com/angular/angular/issues/37583)) ([8fbf40b](https://github.com/angular/angular/commit/8fbf40b))
|
||||
* **forms:** AbstractControl to store raw validators in addition to combined validators function ([#37881](https://github.com/angular/angular/issues/37881)) ([ad7046b](https://github.com/angular/angular/commit/ad7046b))
|
||||
* **localize:** allow duplicate messages to be handled during extraction ([#38082](https://github.com/angular/angular/issues/38082)) ([cf9a47b](https://github.com/angular/angular/commit/cf9a47b)), closes [#38077](https://github.com/angular/angular/issues/38077)
|
||||
* **localize:** expose `canParse()` diagnostics ([#37909](https://github.com/angular/angular/issues/37909)) ([ec32eba](https://github.com/angular/angular/commit/ec32eba)), closes [#37901](https://github.com/angular/angular/issues/37901)
|
||||
* **localize:** implement message extraction tool ([#32912](https://github.com/angular/angular/issues/32912)) ([190561d](https://github.com/angular/angular/commit/190561d))
|
||||
* **platform-browser:** Allow `sms`-URLs ([#31463](https://github.com/angular/angular/issues/31463)) ([fc5c34d](https://github.com/angular/angular/commit/fc5c34d)), closes [#31462](https://github.com/angular/angular/issues/31462)
|
||||
* **platform-server:** add option for absolute URL HTTP support ([#37539](https://github.com/angular/angular/issues/37539)) ([d37049a](https://github.com/angular/angular/commit/d37049a)), closes [#37071](https://github.com/angular/angular/issues/37071)
|
||||
* **router:** better warning message when a router outlet has not been instantiated ([#30246](https://github.com/angular/angular/issues/30246)) ([1609815](https://github.com/angular/angular/commit/1609815))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** fix integration test for bazel building ([#38629](https://github.com/angular/angular/issues/38629)) ([dd82f2f](https://github.com/angular/angular/commit/dd82f2f))
|
||||
* **common:** date pipe gives wrong week number ([#37632](https://github.com/angular/angular/issues/37632)) ([ef1fb6d](https://github.com/angular/angular/commit/ef1fb6d)), closes [#33961](https://github.com/angular/angular/issues/33961)
|
||||
* **common:** narrow `NgIf` context variables in template type checker ([#36627](https://github.com/angular/angular/issues/36627)) ([9c8bc4a](https://github.com/angular/angular/commit/9c8bc4a))
|
||||
* **compiler-cli:** avoid creating value expressions for symbols from type-only imports ([#37912](https://github.com/angular/angular/issues/37912)) ([18098d3](https://github.com/angular/angular/commit/18098d3)), closes [#37900](https://github.com/angular/angular/issues/37900)
|
||||
* **compiler-cli:** ensure source-maps can handle webpack:// protocol ([#32912](https://github.com/angular/angular/issues/32912)) ([decd95e](https://github.com/angular/angular/commit/decd95e))
|
||||
* **compiler-cli:** only read source-map comment from last line ([#32912](https://github.com/angular/angular/issues/32912)) ([07a07e3](https://github.com/angular/angular/commit/07a07e3))
|
||||
* **compiler-cli:** type-check inputs that include undefined when there's coercion members ([#38273](https://github.com/angular/angular/issues/38273)) ([7525f3a](https://github.com/angular/angular/commit/7525f3a))
|
||||
* **compiler:** incorrectly inferring namespace for HTML nodes inside SVG ([#38477](https://github.com/angular/angular/issues/38477)) ([0dda97e](https://github.com/angular/angular/commit/0dda97e)), closes [#37218](https://github.com/angular/angular/issues/37218)
|
||||
* **compiler:** mark `NgModuleFactory` construction as not side effectful ([#38147](https://github.com/angular/angular/issues/38147)) ([7f8c222](https://github.com/angular/angular/commit/7f8c222))
|
||||
* **core:** Allow modification of lifecycle hooks any time before bootstrap ([#35464](https://github.com/angular/angular/issues/35464)) ([737506e](https://github.com/angular/angular/commit/737506e)), closes [#30497](https://github.com/angular/angular/issues/30497)
|
||||
* **core:** detect DI parameters in JIT mode for downleveled ES2015 classes ([#38463](https://github.com/angular/angular/issues/38463)) ([ca07da4](https://github.com/angular/angular/commit/ca07da4)), closes [#38453](https://github.com/angular/angular/issues/38453)
|
||||
* **core:** determine required DOMParser feature availability ([#36578](https://github.com/angular/angular/issues/36578)) ([#36578](https://github.com/angular/angular/issues/36578)) ([c509243](https://github.com/angular/angular/commit/c509243))
|
||||
* **core:** do not trigger CSP alert/report in Firefox and Chrome ([#36578](https://github.com/angular/angular/issues/36578)) ([#36578](https://github.com/angular/angular/issues/36578)) ([b950d46](https://github.com/angular/angular/commit/b950d46)), closes [#25214](https://github.com/angular/angular/issues/25214)
|
||||
* **core:** move generated i18n statements to the `consts` field of ComponentDef ([#38404](https://github.com/angular/angular/issues/38404)) ([cb05c01](https://github.com/angular/angular/commit/cb05c01))
|
||||
* **elements:** run strategy methods in correct zone ([#37814](https://github.com/angular/angular/issues/37814)) ([8df888d](https://github.com/angular/angular/commit/8df888d)), closes [#24181](https://github.com/angular/angular/issues/24181)
|
||||
* **forms:** handle form groups/arrays own pending async validation ([#22575](https://github.com/angular/angular/issues/22575)) ([77b62a5](https://github.com/angular/angular/commit/77b62a5)), closes [#10064](https://github.com/angular/angular/issues/10064)
|
||||
* **language-service:** non-existent module format in package output ([#37623](https://github.com/angular/angular/issues/37623)) ([413a0fb](https://github.com/angular/angular/commit/413a0fb))
|
||||
* **localize:** ensure required XLIFF parameters are serialized ([#38575](https://github.com/angular/angular/issues/38575)) ([f0af387](https://github.com/angular/angular/commit/f0af387)), closes [#38570](https://github.com/angular/angular/issues/38570)
|
||||
* **localize:** extract the correct message ids ([#38498](https://github.com/angular/angular/issues/38498)) ([ac461e1](https://github.com/angular/angular/commit/ac461e1))
|
||||
* **localize:** render ICU placeholders in extracted translation files ([#38484](https://github.com/angular/angular/issues/38484)) ([81c3e80](https://github.com/angular/angular/commit/81c3e80))
|
||||
* **localize:** render text of extracted placeholders ([#38536](https://github.com/angular/angular/issues/38536)) ([14e90be](https://github.com/angular/angular/commit/14e90be))
|
||||
* **ngcc:** detect synthesized delegate constructors for downleveled ES2015 classes ([#38463](https://github.com/angular/angular/issues/38463)) ([3b9c802](https://github.com/angular/angular/commit/3b9c802)), closes [#38453](https://github.com/angular/angular/issues/38453) [#38453](https://github.com/angular/angular/issues/38453)
|
||||
* **router:** defer loading of wildcard module until needed ([#38348](https://github.com/angular/angular/issues/38348)) ([8f708b5](https://github.com/angular/angular/commit/8f708b5)), closes [#25494](https://github.com/angular/angular/issues/25494)
|
||||
* **router:** fix navigation ignoring logic to compare to the browser url ([#37716](https://github.com/angular/angular/issues/37716)) ([a5ffca0](https://github.com/angular/angular/commit/a5ffca0)), closes [#16710](https://github.com/angular/angular/issues/16710) [#13586](https://github.com/angular/angular/issues/13586)
|
||||
* **router:** properly compare array queryParams for equality ([#37709](https://github.com/angular/angular/issues/37709)) ([#37860](https://github.com/angular/angular/issues/37860)) ([1801d0c](https://github.com/angular/angular/commit/1801d0c))
|
||||
* **router:** remove parenthesis for primary outlet segment after removing auxiliary outlet segment ([#24656](https://github.com/angular/angular/issues/24656)) ([#37163](https://github.com/angular/angular/issues/37163)) ([71f008f](https://github.com/angular/angular/commit/71f008f))
|
||||
* **router:** restore 'history.state' object for navigations coming from Angular router ([#28108](https://github.com/angular/angular/issues/28108)) ([#28176](https://github.com/angular/angular/issues/28176)) ([df76a20](https://github.com/angular/angular/commit/df76a20))
|
||||
|
||||
### Code Refactoring
|
||||
* **router:** export DefaultRouteReuseStrategy to Router public_api ([#31575](https://github.com/angular/angular/issues/31575)) ([ca79880](https://github.com/angular/angular/commit/ca79880))
|
||||
|
||||
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
* **compiler-cli:** don't emit template guards when child scope is empty ([#38418](https://github.com/angular/angular/issues/38418)) ([1388c17](https://github.com/angular/angular/commit/1388c17))
|
||||
* **compiler-cli:** fix regressions in incremental program reuse ([#37641](https://github.com/angular/angular/issues/37641)) ([5103d90](https://github.com/angular/angular/commit/5103d90))
|
||||
* **compiler-cli:** only generate directive declarations when used ([#38418](https://github.com/angular/angular/issues/38418)) ([fb8f4b4](https://github.com/angular/angular/commit/fb8f4b4))
|
||||
* **compiler-cli:** only generate type-check code for referenced DOM elements ([#38418](https://github.com/angular/angular/issues/38418)) ([f42e6ce](https://github.com/angular/angular/commit/f42e6ce))
|
||||
* **forms:** use internal `ngDevMode` flag to tree-shake error messages in prod builds ([#37821](https://github.com/angular/angular/issues/37821)) ([201a546](https://github.com/angular/angular/commit/201a546)), closes [#37697](https://github.com/angular/angular/issues/37697)
|
||||
* **ngcc:** shortcircuit tokenizing in ESM dependency host ([#37639](https://github.com/angular/angular/issues/37639)) ([bd7f440](https://github.com/angular/angular/commit/bd7f440))
|
||||
* **ngcc:** use `EntryPointManifest` to speed up noop `ProgramBaseEntryPointFinder` ([#37665](https://github.com/angular/angular/issues/37665)) ([9318e23](https://github.com/angular/angular/commit/9318e23))
|
||||
* **router:** apply prioritizedGuardValue operator to optimize CanLoad guards ([#37523](https://github.com/angular/angular/issues/37523)) ([d7dd295](https://github.com/angular/angular/commit/d7dd295))
|
||||
|
||||
|
||||
|
||||
@ -13,29 +100,6 @@
|
||||
## 10.0.14 (2020-08-26)
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.8"></a>
|
||||
# 10.1.0-next.8 (2020-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **localize:** extract the correct message ids ([#38498](https://github.com/angular/angular/issues/38498)) ([ac461e1](https://github.com/angular/angular/commit/ac461e1))
|
||||
* **router:** support lazy loading for empty path named outlets ([#38379](https://github.com/angular/angular/issues/38379)) ([7ad3264](https://github.com/angular/angular/commit/7ad3264)), closes [#12842](https://github.com/angular/angular/issues/12842)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** support unary operators for more accurate type checking ([#37918](https://github.com/angular/angular/issues/37918)) ([874792d](https://github.com/angular/angular/commit/874792d)), closes [#20845](https://github.com/angular/angular/issues/20845) [#36178](https://github.com/angular/angular/issues/36178)
|
||||
* **compiler-cli:** add support for TypeScript 4.0 ([#38076](https://github.com/angular/angular/issues/38076)) ([0fc44e0](https://github.com/angular/angular/commit/0fc44e0))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **forms:** use internal `ngDevMode` flag to tree-shake error messages in prod builds ([#37821](https://github.com/angular/angular/issues/37821)) ([201a546](https://github.com/angular/angular/commit/201a546)), closes [#37697](https://github.com/angular/angular/issues/37697)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.12"></a>
|
||||
## 10.0.12 (2020-08-24)
|
||||
|
||||
@ -53,13 +117,6 @@
|
||||
* **language-service:** introduce hybrid visitor to locate AST node ([#38540](https://github.com/angular/angular/issues/38540)) ([66d8c22](https://github.com/angular/angular/commit/66d8c22))
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.7"></a>
|
||||
# 10.1.0-next.7 (2020-08-19)
|
||||
|
||||
This release contains various API docs improvements.
|
||||
|
||||
|
||||
<a name="10.0.11"></a>
|
||||
## 10.0.11 (2020-08-19)
|
||||
|
||||
@ -70,29 +127,6 @@ This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.6"></a>
|
||||
# 10.1.0-next.6 (2020-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** detect DI parameters in JIT mode for downleveled ES2015 classes ([#38463](https://github.com/angular/angular/issues/38463)) ([ca07da4](https://github.com/angular/angular/commit/ca07da4)), closes [#38453](https://github.com/angular/angular/issues/38453)
|
||||
* **core:** move generated i18n statements to the `consts` field of ComponentDef ([#38404](https://github.com/angular/angular/issues/38404)) ([cb05c01](https://github.com/angular/angular/commit/cb05c01))
|
||||
* **localize:** render ICU placeholders in extracted translation files ([#38484](https://github.com/angular/angular/issues/38484)) ([81c3e80](https://github.com/angular/angular/commit/81c3e80))
|
||||
* **ngcc:** detect synthesized delegate constructors for downleveled ES2015 classes ([#38463](https://github.com/angular/angular/issues/38463)) ([3b9c802](https://github.com/angular/angular/commit/3b9c802)), closes [#38453](https://github.com/angular/angular/issues/38453) [#38453](https://github.com/angular/angular/issues/38453)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler-cli:** don't emit template guards when child scope is empty ([#38418](https://github.com/angular/angular/issues/38418)) ([1388c17](https://github.com/angular/angular/commit/1388c17))
|
||||
* **compiler-cli:** only generate directive declarations when used ([#38418](https://github.com/angular/angular/issues/38418)) ([fb8f4b4](https://github.com/angular/angular/commit/fb8f4b4))
|
||||
* **compiler-cli:** only generate type-check code for referenced DOM elements ([#38418](https://github.com/angular/angular/issues/38418)) ([f42e6ce](https://github.com/angular/angular/commit/f42e6ce))
|
||||
|
||||
### Code Refactoring
|
||||
* **router:** export DefaultRouteReuseStrategy to Router public_api ([#31575](https://github.com/angular/angular/issues/31575)) ([ca79880](https://github.com/angular/angular/commit/ca79880))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.10"></a>
|
||||
## 10.0.10 (2020-08-17)
|
||||
|
||||
@ -108,25 +142,6 @@ This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.5"></a>
|
||||
# 10.1.0-next.5 (2020-08-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** avoid creating value expressions for symbols from type-only imports ([#37912](https://github.com/angular/angular/issues/37912)) ([18098d3](https://github.com/angular/angular/commit/18098d3)), closes [#37900](https://github.com/angular/angular/issues/37900)
|
||||
* **compiler-cli:** type-check inputs that include undefined when there's coercion members ([#38273](https://github.com/angular/angular/issues/38273)) ([7525f3a](https://github.com/angular/angular/commit/7525f3a))
|
||||
* **router:** defer loading of wildcard module until needed ([#38348](https://github.com/angular/angular/issues/38348)) ([8f708b5](https://github.com/angular/angular/commit/8f708b5)), closes [#25494](https://github.com/angular/angular/issues/25494)
|
||||
* **router:** restore 'history.state' object for navigations coming from Angular router ([#28108](https://github.com/angular/angular/issues/28108)) ([#28176](https://github.com/angular/angular/issues/28176)) ([df76a20](https://github.com/angular/angular/commit/df76a20))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-cli:** Add compiler option to report errors when assigning to restricted input fields ([#38249](https://github.com/angular/angular/issues/38249)) ([71138f6](https://github.com/angular/angular/commit/71138f6))
|
||||
* **router:** better warning message when a router outlet has not been instantiated ([#30246](https://github.com/angular/angular/issues/30246)) ([1609815](https://github.com/angular/angular/commit/1609815))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.9"></a>
|
||||
## 10.0.9 (2020-08-12)
|
||||
|
||||
@ -148,24 +163,6 @@ This release contains various API docs improvements.
|
||||
* **service-worker:** fix the chrome debugger syntax highlighter ([#38332](https://github.com/angular/angular/issues/38332)) ([f5d5bac](https://github.com/angular/angular/commit/f5d5bac))
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.4"></a>
|
||||
# 10.1.0-next.4 (2020-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** narrow `NgIf` context variables in template type checker ([#36627](https://github.com/angular/angular/issues/36627)) ([9c8bc4a](https://github.com/angular/angular/commit/9c8bc4a))
|
||||
* **compiler:** mark `NgModuleFactory` construction as not side effectful ([#38147](https://github.com/angular/angular/issues/38147)) ([7f8c222](https://github.com/angular/angular/commit/7f8c222))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** rename async to waitForAsync to avoid confusing ([#37583](https://github.com/angular/angular/issues/37583)) ([8f07429](https://github.com/angular/angular/commit/8f07429))
|
||||
* **core:** update reference and doc to change `async` to `waitAsync`. ([#37583](https://github.com/angular/angular/issues/37583)) ([8fbf40b](https://github.com/angular/angular/commit/8fbf40b))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.8"></a>
|
||||
## 10.0.8 (2020-08-04)
|
||||
|
||||
@ -187,16 +184,6 @@ This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.3"></a>
|
||||
# 10.1.0-next.3 (2020-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **elements:** run strategy methods in correct zone ([#37814](https://github.com/angular/angular/issues/37814)) ([8df888d](https://github.com/angular/angular/commit/8df888d)), closes [#24181](https://github.com/angular/angular/issues/24181)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.6"></a>
|
||||
## 10.0.6 (2020-07-28)
|
||||
|
||||
@ -210,23 +197,6 @@ This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.2"></a>
|
||||
# 10.1.0-next.2 (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Allow modification of lifecycle hooks any time before bootstrap ([#35464](https://github.com/angular/angular/issues/35464)) ([737506e](https://github.com/angular/angular/commit/737506e)), closes [#30497](https://github.com/angular/angular/issues/30497)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** add ReadonlyMap in place of Map in keyValuePipe ([#37311](https://github.com/angular/angular/issues/37311)) ([3373453](https://github.com/angular/angular/commit/3373453)), closes [#37308](https://github.com/angular/angular/issues/37308)
|
||||
* **forms:** AbstractControl to store raw validators in addition to combined validators function ([#37881](https://github.com/angular/angular/issues/37881)) ([ad7046b](https://github.com/angular/angular/commit/ad7046b))
|
||||
* **localize:** allow duplicate messages to be handled during extraction ([#38082](https://github.com/angular/angular/issues/38082)) ([cf9a47b](https://github.com/angular/angular/commit/cf9a47b)), closes [#38077](https://github.com/angular/angular/issues/38077)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.5"></a>
|
||||
## 10.0.5 (2020-07-22)
|
||||
|
||||
@ -261,62 +231,6 @@ This release contains various API docs improvements.
|
||||
* **bazel:** provide LinkablePackageInfo from ng_module ([#37778](https://github.com/angular/angular/issues/37778)) ([6cd10a1](https://github.com/angular/angular/commit/6cd10a1)), closes [/github.com/bazelbuild/rules_nodejs/blob/9a5de3728b05bf1647bbb87ad99f54e626604705/internal/linker/link_node_modules.bzl#L144-L146](https://github.com//github.com/bazelbuild/rules_nodejs/blob/9a5de3728b05bf1647bbb87ad99f54e626604705/internal/linker/link_node_modules.bzl/issues/L144-L146)
|
||||
|
||||
|
||||
<a name="10.1.0-next.1"></a>
|
||||
# 10.1.0-next.1 (2020-07-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** ng_module rule does not expose flat module information in Ivy ([#36971](https://github.com/angular/angular/issues/36971)) ([1550663](https://github.com/angular/angular/commit/1550663))
|
||||
* **compiler:** check more cases for pipe usage inside host bindings ([#37883](https://github.com/angular/angular/issues/37883)) ([9322b9a](https://github.com/angular/angular/commit/9322b9a)), closes [#34655](https://github.com/angular/angular/issues/34655) [#37610](https://github.com/angular/angular/issues/37610)
|
||||
* **compiler-cli:** ensure file_system handles mixed Windows drives ([#37959](https://github.com/angular/angular/issues/37959)) ([6b31155](https://github.com/angular/angular/commit/6b31155)), closes [#36777](https://github.com/angular/angular/issues/36777)
|
||||
* **language-service:** remove completion for string ([#37983](https://github.com/angular/angular/issues/37983)) ([10aba15](https://github.com/angular/angular/commit/10aba15))
|
||||
* **ngcc:** report a warning if ngcc tries to use a solution-style tsconfig ([#38003](https://github.com/angular/angular/issues/38003)) ([b358495](https://github.com/angular/angular/commit/b358495)), closes [#36386](https://github.com/angular/angular/issues/36386)
|
||||
* **router:** ensure duplicate popstate/hashchange events are handled correctly ([#37674](https://github.com/angular/angular/issues/37674)) ([9185c6e](https://github.com/angular/angular/commit/9185c6e)), closes [/github.com/angular/angular/issues/16710#issuecomment-646919529](https://github.com//github.com/angular/angular/issues/16710/issues/issuecomment-646919529) [#16710](https://github.com/angular/angular/issues/16710)
|
||||
* **service-worker:** correctly handle relative base href ([#37922](https://github.com/angular/angular/issues/37922)) ([d19ef65](https://github.com/angular/angular/commit/d19ef65)), closes [#25055](https://github.com/angular/angular/issues/25055) [#25055](https://github.com/angular/angular/issues/25055)
|
||||
* **service-worker:** correctly serve `ngsw/state` with a non-root SW scope ([#37922](https://github.com/angular/angular/issues/37922)) ([2156bee](https://github.com/angular/angular/commit/2156bee)), closes [#30505](https://github.com/angular/angular/issues/30505)
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0-next.0"></a>
|
||||
# 10.1.0-next.0 (2020-07-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** date pipe gives wrong week number ([#37632](https://github.com/angular/angular/issues/37632)) ([ef1fb6d](https://github.com/angular/angular/commit/ef1fb6d)), closes [#33961](https://github.com/angular/angular/issues/33961)
|
||||
* **compiler-cli:** ensure source-maps can handle webpack:// protocol ([#32912](https://github.com/angular/angular/issues/32912)) ([decd95e](https://github.com/angular/angular/commit/decd95e))
|
||||
* **compiler-cli:** only read source-map comment from last line ([#32912](https://github.com/angular/angular/issues/32912)) ([07a07e3](https://github.com/angular/angular/commit/07a07e3))
|
||||
* **core:** determine required DOMParser feature availability ([#36578](https://github.com/angular/angular/issues/36578)) ([#36578](https://github.com/angular/angular/issues/36578)) ([c509243](https://github.com/angular/angular/commit/c509243))
|
||||
* **core:** do not trigger CSP alert/report in Firefox and Chrome ([#36578](https://github.com/angular/angular/issues/36578)) ([#36578](https://github.com/angular/angular/issues/36578)) ([b950d46](https://github.com/angular/angular/commit/b950d46)), closes [#25214](https://github.com/angular/angular/issues/25214)
|
||||
* **forms:** handle form groups/arrays own pending async validation ([#22575](https://github.com/angular/angular/issues/22575)) ([77b62a5](https://github.com/angular/angular/commit/77b62a5)), closes [#10064](https://github.com/angular/angular/issues/10064)
|
||||
* **language-service:** non-existent module format in package output ([#37623](https://github.com/angular/angular/issues/37623)) ([413a0fb](https://github.com/angular/angular/commit/413a0fb))
|
||||
* **router:** fix navigation ignoring logic to compare to the browser url ([#37716](https://github.com/angular/angular/issues/37716)) ([a5ffca0](https://github.com/angular/angular/commit/a5ffca0)), closes [#16710](https://github.com/angular/angular/issues/16710) [#13586](https://github.com/angular/angular/issues/13586)
|
||||
* **router:** properly compare array queryParams for equality ([#37709](https://github.com/angular/angular/issues/37709)) ([#37860](https://github.com/angular/angular/issues/37860)) ([1801d0c](https://github.com/angular/angular/commit/1801d0c))
|
||||
* **router:** remove parenthesis for primary outlet segment after removing auxiliary outlet segment ([#24656](https://github.com/angular/angular/issues/24656)) ([#37163](https://github.com/angular/angular/issues/37163)) ([71f008f](https://github.com/angular/angular/commit/71f008f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** provide LinkablePackageInfo from ng_module ([#37623](https://github.com/angular/angular/issues/37623)) ([6898eab](https://github.com/angular/angular/commit/6898eab))
|
||||
* **compiler-cli:** add `SourceFile.getOriginalLocation()` to sourcemaps package ([#32912](https://github.com/angular/angular/issues/32912)) ([6abb8d0](https://github.com/angular/angular/commit/6abb8d0))
|
||||
* **compiler-cli:** explain why an expression cannot be used in AOT compilations ([#37587](https://github.com/angular/angular/issues/37587)) ([712f1bd](https://github.com/angular/angular/commit/712f1bd))
|
||||
* **core:** support injection token as predicate in queries ([#37506](https://github.com/angular/angular/issues/37506)) ([97dc85b](https://github.com/angular/angular/commit/97dc85b)), closes [#21152](https://github.com/angular/angular/issues/21152) [#36144](https://github.com/angular/angular/issues/36144)
|
||||
* **localize:** expose `canParse()` diagnostics ([#37909](https://github.com/angular/angular/issues/37909)) ([ec32eba](https://github.com/angular/angular/commit/ec32eba)), closes [#37901](https://github.com/angular/angular/issues/37901)
|
||||
* **localize:** implement message extraction tool ([#32912](https://github.com/angular/angular/issues/32912)) ([190561d](https://github.com/angular/angular/commit/190561d))
|
||||
* **platform-browser:** Allow `sms`-URLs ([#31463](https://github.com/angular/angular/issues/31463)) ([fc5c34d](https://github.com/angular/angular/commit/fc5c34d)), closes [#31462](https://github.com/angular/angular/issues/31462)
|
||||
* **platform-server:** add option for absolute URL HTTP support ([#37539](https://github.com/angular/angular/issues/37539)) ([d37049a](https://github.com/angular/angular/commit/d37049a)), closes [#37071](https://github.com/angular/angular/issues/37071)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler-cli:** fix regressions in incremental program reuse ([#37641](https://github.com/angular/angular/issues/37641)) ([5103d90](https://github.com/angular/angular/commit/5103d90))
|
||||
* **ngcc:** shortcircuit tokenizing in ESM dependency host ([#37639](https://github.com/angular/angular/issues/37639)) ([bd7f440](https://github.com/angular/angular/commit/bd7f440))
|
||||
* **ngcc:** use `EntryPointManifest` to speed up noop `ProgramBaseEntryPointFinder` ([#37665](https://github.com/angular/angular/issues/37665)) ([9318e23](https://github.com/angular/angular/commit/9318e23))
|
||||
* **router:** apply prioritizedGuardValue operator to optimize CanLoad guards ([#37523](https://github.com/angular/angular/issues/37523)) ([d7dd295](https://github.com/angular/angular/commit/d7dd295))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.3"></a>
|
||||
## 10.0.3 (2020-07-08)
|
||||
|
||||
@ -598,12 +512,12 @@ https://github.com/microsoft/TypeScript/issues/38374 for more
|
||||
information and updates.
|
||||
|
||||
If you used Closure Compiler with Angular in the past, you will likely
|
||||
be better off consuming Angular packages built from sources directly
|
||||
be better off consuming Angular packages built from sources directly
|
||||
rather than consuming the version we publish on npm,
|
||||
which is primarily optimized for Webpack/Rollup + Terser build pipeline.
|
||||
|
||||
As a temporary workaround, you might consider using your current build
|
||||
pipeline with Closure flag `--compilation_level=SIMPLE`. This flag
|
||||
pipeline with Closure flag `--compilation_level=SIMPLE`. This flag
|
||||
will ensure that your build pipeline produces buildable and
|
||||
runnable artifacts, at the cost of increased payload size due to
|
||||
advanced optimizations being disabled.
|
||||
@ -611,17 +525,17 @@ advanced optimizations being disabled.
|
||||
If you were affected by this change, please help us understand your
|
||||
needs by leaving a comment on https://github.com/angular/angular/issues/37234.
|
||||
|
||||
* **core:** make generic mandatory for ModuleWithProviders
|
||||
* **core:** make generic mandatory for ModuleWithProviders
|
||||
|
||||
A generic type parameter has always been required for the `ModuleWithProviders` pattern to work with Ivy, but prior to this commit, View Engine allowed the generic type to be omitted (though support was officially deprecated).
|
||||
If you're using `ModuleWithProviders` without a generic type in your application code, a v10 migration will update your code for you.
|
||||
If you're using `ModuleWithProviders` without a generic type in your application code, a v10 migration will update your code for you.
|
||||
|
||||
However, if you are using View Engine and also depending on a library that omits the generic type, you will now get a build time error similar to:
|
||||
|
||||
```
|
||||
error TS2314: Generic type 'ModuleWithProviders<T>' requires 1 type argument(s).
|
||||
```
|
||||
|
||||
|
||||
In this case, ngcc won't help you (because it's Ivy-only) and the migration only covers application code.
|
||||
You should contact the library author to fix their library to provide a type parameter when they use this class.
|
||||
|
||||
@ -1949,7 +1863,7 @@ API surface going forward.
|
||||
* **core:** Injector.get now accepts abstract classes to return
|
||||
type-safe values. Previous implementation returned `any` through the
|
||||
deprecated implementation.
|
||||
* Angular now compiles with Ivy by default ([#32219](https://github.com/angular/angular/issues/32219)) ([ec4381d](https://github.com/angular/angular/commit/ec4381d)).
|
||||
* Angular now compiles with Ivy by default ([#32219](https://github.com/angular/angular/issues/32219)) ([ec4381d](https://github.com/angular/angular/commit/ec4381d)).
|
||||
|
||||
If you aren't familiar with Ivy, read our [blog post about the Ivy preview](https://blog.angular.io/its-time-for-the-compatibility-opt-in-preview-of-ivy-38f3542a282f?gi=8bfeb44b05c) and see the list of changes [here](https://docs.google.com/document/d/1Dije0AsJ0PxL3NaeNPxpYDeapj30b_QC0xfeIvIIzgg/preview).
|
||||
|
||||
|
@ -230,7 +230,6 @@ Must be one of the following:
|
||||
* **fix**: A bug fix
|
||||
* **perf**: A code change that improves performance
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
|
||||
|
@ -16,13 +16,6 @@ import {BuildNums, PrNums, SHA} from './constants';
|
||||
|
||||
const logger = new Logger('mock-external-apis');
|
||||
|
||||
const log = (...args: any[]) => {
|
||||
// Filter out non-matching URL checks
|
||||
if (!/^matching.+: false$/.test(args[0])) {
|
||||
logger.log(...args);
|
||||
}
|
||||
};
|
||||
|
||||
const AIO_CIRCLE_CI_TOKEN = getEnvVar('AIO_CIRCLE_CI_TOKEN');
|
||||
const AIO_GITHUB_TOKEN = getEnvVar('AIO_GITHUB_TOKEN');
|
||||
|
||||
@ -91,8 +84,8 @@ const createArchive = (buildNum: number, prNum: number, sha: string) => {
|
||||
};
|
||||
|
||||
// Create request scopes
|
||||
const circleCiApi = nock(CIRCLE_CI_API_HOST).log(log).persist();
|
||||
const githubApi = nock(GITHUB_API_HOST).log(log).persist().matchHeader('Authorization', `token ${AIO_GITHUB_TOKEN}`);
|
||||
const circleCiApi = nock(CIRCLE_CI_API_HOST).persist();
|
||||
const githubApi = nock(GITHUB_API_HOST).persist().matchHeader('Authorization', `token ${AIO_GITHUB_TOKEN}`);
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
|
@ -27,28 +27,28 @@
|
||||
"body-parser": "^1.19.0",
|
||||
"delete-empty": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"jasmine": "^3.5.0",
|
||||
"nock": "^12.0.3",
|
||||
"node-fetch": "^2.6.0",
|
||||
"jasmine": "^3.6.1",
|
||||
"nock": "^13.0.4",
|
||||
"node-fetch": "^2.6.1",
|
||||
"shelljs": "^0.8.4",
|
||||
"source-map-support": "^0.5.19",
|
||||
"tar-stream": "^2.1.2",
|
||||
"tslib": "^1.11.1"
|
||||
"tar-stream": "^2.1.3",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jasmine": "^3.5.10",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/jasmine": "^3.5.14",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^13.13.2",
|
||||
"@types/node": "^14.6.4",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/shelljs": "^0.8.7",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"nodemon": "^2.0.3",
|
||||
"@types/shelljs": "^0.8.8",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"nodemon": "^2.0.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"supertest": "^4.0.2",
|
||||
"tslint": "^6.1.1",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
||||
"typescript": "^3.8.3"
|
||||
"typescript": "^4.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -214,23 +214,24 @@ describe('GithubApi', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should call \'https.request()\' with the correct options', () => {
|
||||
it('should call \'https.request()\' with the correct options', async () => {
|
||||
const requestHandler = nock('https://api.github.com')
|
||||
.intercept('/path', 'method')
|
||||
.reply(200);
|
||||
|
||||
(api as any).request('method', '/path');
|
||||
await (api as any).request('method', '/path');
|
||||
requestHandler.done();
|
||||
});
|
||||
|
||||
|
||||
it('should add the \'Authorization\' header containing the \'githubToken\'', () => {
|
||||
it('should add the \'Authorization\' header containing the \'githubToken\'', async () => {
|
||||
const requestHandler = nock('https://api.github.com')
|
||||
.intercept('/path', 'method', undefined, {
|
||||
reqheaders: {Authorization: 'token 12345'},
|
||||
})
|
||||
.reply(200);
|
||||
(api as any).request('method', '/path');
|
||||
|
||||
await (api as any).request('method', '/path');
|
||||
requestHandler.done();
|
||||
});
|
||||
|
||||
@ -244,12 +245,13 @@ describe('GithubApi', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should \'JSON.stringify\' and send the data along with the request', () => {
|
||||
it('should \'JSON.stringify\' and send the data along with the request', async () => {
|
||||
const data = {key: 'value'};
|
||||
const requestHandler = nock('https://api.github.com')
|
||||
.intercept('/path', 'method', JSON.stringify(data))
|
||||
.reply(200);
|
||||
(api as any).request('method', '/path', data);
|
||||
|
||||
await (api as any).request('method', '/path', data);
|
||||
requestHandler.done();
|
||||
});
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -41,7 +41,6 @@
|
||||
<!-- #enddocregion translated-plural -->
|
||||
<!-- #docregion translated-select -->
|
||||
<!-- #docregion translate-select-1 -->
|
||||
</trans-unit>
|
||||
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
|
||||
<source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
|
||||
<target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
|
||||
|
@ -10,8 +10,8 @@ import { Hero } from '../shared/hero.model';
|
||||
template: `
|
||||
<section>
|
||||
Our list of heroes:
|
||||
<hero-profile *ngFor="let hero of heroes" [hero]="hero">
|
||||
</hero-profile>
|
||||
<toh-hero *ngFor="let hero of heroes" [hero]="hero">
|
||||
</toh-hero>
|
||||
Total powers: {{totalPowers}}<br>
|
||||
Average power: {{totalPowers / heroes.length}}
|
||||
</section>
|
||||
|
@ -62,7 +62,7 @@ In the following example, the `@Component()` metadata object and the class const
|
||||
@Component({
|
||||
selector: 'app-typical',
|
||||
template: '<div>A typical component for {{data.name}}</div>'
|
||||
)}
|
||||
})
|
||||
export class TypicalComponent {
|
||||
@Input() data: TypicalData;
|
||||
constructor(private someService: SomeService) { ... }
|
||||
|
@ -125,7 +125,7 @@ Emulated is the default and most commonly used view encapsulation. For more info
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
The shadow-piercing descendant combinator is deprecated and [support is being removed from major browsers](https://www.chromestatus.com/features/6750456638341120) and tools.
|
||||
The shadow-piercing descendant combinator is deprecated and [support is being removed from major browsers](https://www.chromestatus.com/feature/6750456638341120) and tools.
|
||||
As such we plan to drop support in Angular (for all 3 of `/deep/`, `>>>` and `::ng-deep`).
|
||||
Until then `::ng-deep` should be preferred for a broader compatibility with the tools.
|
||||
|
||||
|
@ -26,7 +26,7 @@ The `ng generate` command creates the `projects/my-lib` folder in your workspace
|
||||
|
||||
</div>
|
||||
|
||||
When you generate a new library, the workspace configuration file, `angular.json`, is updated with a project of type 'library'.
|
||||
When you generate a new library, the workspace configuration file, `angular.json`, is updated with a project of type `library`.
|
||||
|
||||
<code-example format="json">
|
||||
"projects": {
|
||||
@ -109,7 +109,7 @@ If you want a dropdown that would contain different passed-in values each time,
|
||||
|
||||
Suppose you want to read a configuration file and then generate a form based on that configuration.
|
||||
If that form will need additional customization by the developer who is using your library, it might work best as a schematic.
|
||||
However, if the forms will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form.
|
||||
However, if the form will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form.
|
||||
In general, the more complex the customization, the more useful the schematic approach.
|
||||
|
||||
To learn more, see [Schematics Overview](guide/schematics) and [Schematics for Libraries](guide/schematics-for-libraries).
|
||||
|
@ -511,9 +511,9 @@ Each script tag has a `type="module"` or `nomodule` attribute. Browsers with nat
|
||||
|
||||
To include differential loading in your application builds, you must configure the Browserslist and TypeScript configuration files in your application project.
|
||||
|
||||
The following examples show a `browserlistrc` and `tsconfig.json` file for a newly created Angular application. In this configuration, legacy browsers such as IE 9-11 are ignored, and the compilation target is ES2015.
|
||||
The following examples show a `.browserslistrc` and `tsconfig.json` file for a newly created Angular application. In this configuration, legacy browsers such as IE 9-11 are ignored, and the compilation target is ES2015.
|
||||
|
||||
<code-example language="none" header="browserslistrc">
|
||||
<code-example language="none" header=".browserslistrc">
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
@ -527,7 +527,7 @@ The following examples show a `browserlistrc` and `tsconfig.json` file for a new
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major version
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
|
@ -76,6 +76,12 @@ All router components must be entry components. Because this would require you t
|
||||
|
||||
## The `entryComponents` array
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Since 9.0.0 with Ivy, the `entryComponents` property is no longer necessary. See [deprecations guide](guide/deprecations#entryComponents).
|
||||
|
||||
</div>
|
||||
|
||||
Though the `@NgModule` decorator has an `entryComponents` array, most of the time
|
||||
you won't have to explicitly set any entry components because Angular adds components listed in `@NgModule.bootstrap` and those in route definitions to entry components automatically. Though these two mechanisms account for most entry components, if your app happens to bootstrap or dynamically load a component by type imperatively,
|
||||
you must add it to `entryComponents` explicitly.
|
||||
|
@ -87,7 +87,7 @@ To make one, enter the following command in the terminal, where `customers` is t
|
||||
ng generate module customers --route customers --module app.module
|
||||
</code-example>
|
||||
|
||||
This creates a `customers` folder with the new lazy-loadable module `CustomersModule` defined in the `customers.module.ts` file. The command automatically declares the `CustomersComponent` inside the new feature module.
|
||||
This creates a `customers` folder having the new lazy-loadable feature module `CustomersModule` defined in the `customers.module.ts` file and the routing module `CustomersRoutingModule` defined in the `customers-routing.module.ts` file. The command automatically declares the `CustomersComponent` and imports `CustomersRoutingModule` inside the new feature module.
|
||||
|
||||
Because the new module is meant to be lazy-loaded, the command does NOT add a reference to the new feature module in the application's root module file, `app.module.ts`.
|
||||
Instead, it adds the declared route, `customers` to the `routes` array declared in the module provided as the `--module` option.
|
||||
|
@ -62,6 +62,8 @@ Angular executes hook methods in the following sequence. You can use them to per
|
||||
|
||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||
|
||||
Note that if your component has no inputs or you use it without providing any inputs, the framework will not call `ngOnChanges()`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
|
@ -141,7 +141,7 @@ Because the token is now an abstract class, and the injectable component impleme
|
||||
The implementation of the method (with all of its code overhead) resides in the injectable component that can be tree-shaken.
|
||||
This allows the parent to communicate with the child (if it is present) in a type-safe manner.
|
||||
|
||||
For example, the `LibCardComponent` now queries`LibHeaderToken` rather than `LibHeaderComponent`.
|
||||
For example, the `LibCardComponent` now queries `LibHeaderToken` rather than `LibHeaderComponent`.
|
||||
The following example shows how the pattern allows `LibCardComponent` to communicate with the `LibHeaderComponent` without actually referring to `LibHeaderComponent`.
|
||||
|
||||
```
|
||||
|
@ -223,6 +223,6 @@ content harmlessly. The following is the browser output
|
||||
of the `evilTitle` examples.
|
||||
|
||||
<code-example language="bash">
|
||||
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
|
||||
"Template alert("evil never sleeps")Syntax" is the property bound evil title.
|
||||
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
|
||||
"Template Syntax" is the property bound evil title.
|
||||
</code-example>
|
||||
|
@ -102,7 +102,7 @@ The following table provides the status for Angular versions under support.
|
||||
Version | Status | Released | Active Ends | LTS Ends
|
||||
------- | ------ | ------------ | ------------ | ------------
|
||||
^10.0.0 | Active | Jun 24, 2020 | Dec 24, 2020 | Dec 24, 2021
|
||||
^9.0.0 | Active | Feb 06, 2020 | Aug 06, 2020 | Aug 06, 2021
|
||||
^9.0.0 | LTS | Feb 06, 2020 | Aug 06, 2020 | Aug 06, 2021
|
||||
^8.0.0 | LTS | May 28, 2019 | Nov 28, 2019 | Nov 28, 2020
|
||||
|
||||
Angular versions ^4.0.0, ^5.0.0, ^6.0.0 and ^7.0.0 are no longer under support.
|
||||
|
@ -53,7 +53,7 @@ RxJS provides many operators, but only a handful are used frequently. For a list
|
||||
|
||||
| Area | Operators |
|
||||
| :------------| :----------|
|
||||
| Creation | `from`,`fromEvent`, `of` |
|
||||
| Creation | `from`, `fromEvent`, `of` |
|
||||
| Combination | `combineLatest`, `concat`, `merge`, `startWith` , `withLatestFrom`, `zip` |
|
||||
| Filtering | `debounceTime`, `distinctUntilChanged`, `filter`, `take`, `takeUntil` |
|
||||
| Transformation | `bufferTime`, `concatMap`, `map`, `mergeMap`, `scan`, `switchMap` |
|
||||
|
@ -107,7 +107,7 @@ Notice that all of the files the browser needs to render this application are ca
|
||||
<div class="alert is-helpful">
|
||||
Pay attention to two key points:
|
||||
|
||||
1. The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extentions. In some cases, you might want to modify the glob pattern to suit your needs.
|
||||
1. The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extensions. In some cases, you might want to modify the glob pattern to suit your needs.
|
||||
|
||||
1. If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`.
|
||||
</div>
|
||||
|
@ -217,7 +217,7 @@ script:
|
||||
- npm run e2e -- --protractor-config=e2e/protractor-ci.conf.js
|
||||
```
|
||||
|
||||
This does the same things as the Circle CI configuration, except that Travis doesn't come with Chrome, so we use Chromium instead.
|
||||
This does the same things as the CircleCI configuration, except that Travis doesn't come with Chrome, so use Chromium instead.
|
||||
|
||||
Step 2: Commit your changes and push them to your repository.
|
||||
|
||||
|
BIN
aio/content/images/bios/samvloeberghs.jpg
Normal file
BIN
aio/content/images/bios/samvloeberghs.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
aio/content/images/bios/thekiba.jpg
Normal file
BIN
aio/content/images/bios/thekiba.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
@ -810,5 +810,21 @@
|
||||
"website": "kreuzercode.com",
|
||||
"bio": "Kevin is a passionate freelance front-end engineer and Google Developer Expert based in Switzerland. He is a JavaScript enthusiast and fascinated by Angular. Kevin always tries to learn new things, expand his knowledge, and share it with others in the form of blog posts, workshops, podcasts, or presentations. He is a writer for various publications and the most active writer on Angular in-depth in 2019. Contributing to multiple projects and maintaining 7 npm packages, Kevin is also a big believer in open source. Furthermore, Kevin is a big football fan. Since his childhood, he has supported Real Madrid, which you might notice in a lot of his blog posts and tutorials.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"samvloeberghs": {
|
||||
"name": "Sam Vloeberghs",
|
||||
"picture": "samvloeberghs.jpg",
|
||||
"groups": ["GDE"],
|
||||
"twitter": "samvloeberghs",
|
||||
"website": "https://samvloeberghs.be",
|
||||
"bio": "Sam is a freelance software architect and Internet entrepreneur, currently focusing on frontend technologies. Co-organiser of the Belgian Angular conference NG-BE and Angular Belgium Meetup group."
|
||||
},
|
||||
"thekiba": {
|
||||
"name": "Andrew Grekov",
|
||||
"picture": "thekiba.jpg",
|
||||
"twitter": "thekiba_io",
|
||||
"website": "https://thekiba.io",
|
||||
"bio": "Andrew is a software engineer using Angular and .NET. He spends most of his spare time staying up-to-date, helping other people, and experimenting with web tech.",
|
||||
"groups": ["GDE"]
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +30,10 @@
|
||||
"url": "https://dev.to/t/angular",
|
||||
"title": "DEV Community"
|
||||
},
|
||||
"angular-in-depth": {
|
||||
"desc": "The place where advanced Angular concepts are explained",
|
||||
"url": "https://blog.angularindepth.com",
|
||||
"title": "Angular In Depth"
|
||||
"indepth-dev": {
|
||||
"desc": "Peer-reviewed Angular articles and tutorials.",
|
||||
"url": "https://indepth.dev/angular/",
|
||||
"title": "Angular inDepth"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -63,6 +63,12 @@
|
||||
"logo": "",
|
||||
"title": "NgRuAir",
|
||||
"url": "https://github.com/ngRuAir/ngruair"
|
||||
},
|
||||
"the-deep-dive": {
|
||||
"desc": "The advanced web development podcast about Angular, RxJS, TypeScript and other technologies. English, audio only.",
|
||||
"logo": "https://i.imgur.com/mmE5Feq.jpg",
|
||||
"title": "The Deep Dive",
|
||||
"url": "https://thedeepdive.simplecast.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,6 +435,12 @@
|
||||
"desc": "Jigsaw provides a set of web components based on Angular. It is supporting the development of all applications of Big Data Product of ZTE (https://www.zte.com.cn).",
|
||||
"title": "Awade Jigsaw (Chinese)",
|
||||
"url": "https://jigsaw-zte.gitee.io"
|
||||
},
|
||||
"material-dayjs-adapter": {
|
||||
"desc": "A DayJS implementation of @angular/material's DateAdapter that results in smaller bundle sizes than its MomentJS counterpart.",
|
||||
"rev": true,
|
||||
"title": "material-dayjs-adapter",
|
||||
"url": "https://www.npmjs.com/package/@tabuckner/material-dayjs-adapter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,7 @@ A later part of this tutorial, [Use forms for user input](start/start-forms "Try
|
||||
|
||||
<code-example header="src/app/cart.service.ts" path="getting-started/src/app/cart.service.1.ts"></code-example>
|
||||
|
||||
<div class="alert is-helpful>
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The StackBlitz generator might provide the cart service in `app.module.ts` by default. That differs from the example, which uses a bundle-optimization technique, an `@Injectable()` decorator with the `{ providedIn: 'root' }` statement.
|
||||
For more information about services, see [Introduction to Services and Dependency Injection](guide/architecture-services "Concepts > Intro to Services and DI").
|
||||
@ -236,7 +236,7 @@ For more information about services, see [Introduction to Services and Dependenc
|
||||
<!-- Accessing data with the HTTP client -->
|
||||
|
||||
Servers often return data in the form of a stream.
|
||||
Streams are useful because they make it easy to transform the returned data and make modifications to the way you request that data.
|
||||
Streams are useful because they make it easy to transform the returned data and make modifications to the way you request that data.
|
||||
The Angular HTTP client, `HttpClient`, is a built-in way to fetch data from external APIs and provide them to your app as a stream.
|
||||
|
||||
This section shows you how to use the HTTP client to retrieve shipping prices from an external file.
|
||||
|
@ -99,10 +99,11 @@ describe('site App', function() {
|
||||
describe('scrolling to the top', () => {
|
||||
it('should scroll to the top when navigating to another page', () => {
|
||||
page.navigateTo('guide/security');
|
||||
|
||||
page.scrollTo('bottom');
|
||||
expect(page.getScrollTop()).toBeGreaterThan(0);
|
||||
|
||||
// Navigate to Reference section, then check
|
||||
// Find the navigation item that has the text "api"
|
||||
page.click(page.getNavItem(/reference/i));
|
||||
page.click(page.getNavItem(/api/i));
|
||||
expect(page.locationPath()).toBe('/api');
|
||||
expect(page.getScrollTop()).toBe(0);
|
||||
|
@ -8,6 +8,7 @@ ts_library(
|
||||
],
|
||||
module_name = "@angular/dev-infra-private",
|
||||
deps = [
|
||||
"//dev-infra/caretaker",
|
||||
"//dev-infra/commit-message",
|
||||
"//dev-infra/format",
|
||||
"//dev-infra/pr",
|
||||
|
26
dev-infra/caretaker/BUILD.bazel
Normal file
26
dev-infra/caretaker/BUILD.bazel
Normal file
@ -0,0 +1,26 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "caretaker",
|
||||
srcs = [
|
||||
"cli.ts",
|
||||
],
|
||||
module_name = "@angular/dev-infra-private/caretaker",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/caretaker/check",
|
||||
"@npm//@types/yargs",
|
||||
"@npm//yargs",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "config",
|
||||
srcs = [
|
||||
"config.ts",
|
||||
],
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/utils",
|
||||
],
|
||||
)
|
21
dev-infra/caretaker/check/BUILD.bazel
Normal file
21
dev-infra/caretaker/check/BUILD.bazel
Normal file
@ -0,0 +1,21 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "check",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "@angular/dev-infra-private/caretaker/service-statuses",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/caretaker:config",
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/fs-extra",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/node-fetch",
|
||||
"@npm//@types/yargs",
|
||||
"@npm//multimatch",
|
||||
"@npm//node-fetch",
|
||||
"@npm//typed-graphqlify",
|
||||
"@npm//yaml",
|
||||
"@npm//yargs",
|
||||
],
|
||||
)
|
27
dev-infra/caretaker/check/check.ts
Normal file
27
dev-infra/caretaker/check/check.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {GitClient} from '../../utils/git';
|
||||
import {getCaretakerConfig} from '../config';
|
||||
|
||||
import {printG3Comparison} from './g3';
|
||||
import {printGithubTasks} from './github';
|
||||
import {printServiceStatuses} from './services';
|
||||
|
||||
|
||||
/** Check the status of services which Angular caretakers need to monitor. */
|
||||
export async function checkServiceStatuses(githubToken: string) {
|
||||
/** The configuration for the caretaker commands. */
|
||||
const config = getCaretakerConfig();
|
||||
/** The GitClient for interacting with git and Github. */
|
||||
const git = new GitClient(githubToken, config);
|
||||
|
||||
await printServiceStatuses();
|
||||
await printGithubTasks(git, config.caretaker);
|
||||
await printG3Comparison(git);
|
||||
}
|
39
dev-infra/caretaker/check/cli.ts
Normal file
39
dev-infra/caretaker/check/cli.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {Arguments, Argv, CommandModule} from 'yargs';
|
||||
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
|
||||
import {checkServiceStatuses} from './check';
|
||||
|
||||
|
||||
export interface CaretakerCheckOptions {
|
||||
githubToken: string;
|
||||
}
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
/** Builds the command. */
|
||||
function builder(yargs: Argv) {
|
||||
return addGithubTokenFlag(yargs);
|
||||
}
|
||||
|
||||
/** Handles the command. */
|
||||
async function handler({githubToken}: Arguments<CaretakerCheckOptions>) {
|
||||
await checkServiceStatuses(githubToken);
|
||||
}
|
||||
|
||||
/** yargs command module for checking status information for the repository */
|
||||
export const CheckModule: CommandModule<{}, CaretakerCheckOptions> = {
|
||||
handler,
|
||||
builder,
|
||||
command: 'check',
|
||||
describe: 'Check the status of information the caretaker manages for the repository',
|
||||
};
|
123
dev-infra/caretaker/check/g3.ts
Normal file
123
dev-infra/caretaker/check/g3.ts
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {existsSync, readFileSync} from 'fs-extra';
|
||||
import * as multimatch from 'multimatch';
|
||||
import {join} from 'path';
|
||||
import {parse as parseYaml} from 'yaml';
|
||||
|
||||
import {getRepoBaseDir} from '../../utils/config';
|
||||
import {bold, debug, info} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
|
||||
/** Compare the upstream master to the upstream g3 branch, if it exists. */
|
||||
export async function printG3Comparison(git: GitClient) {
|
||||
const angularRobotFilePath = join(getRepoBaseDir(), '.github/angular-robot.yml');
|
||||
if (!existsSync(angularRobotFilePath)) {
|
||||
return debug('No angular robot configuration file exists, skipping.');
|
||||
}
|
||||
|
||||
/** The configuration defined for the angular robot. */
|
||||
const robotConfig = parseYaml(readFileSync(angularRobotFilePath).toString());
|
||||
/** The files to be included in the g3 sync. */
|
||||
const includeFiles = robotConfig?.merge?.g3Status?.include || [];
|
||||
/** The files to be expected in the g3 sync. */
|
||||
const excludeFiles = robotConfig?.merge?.g3Status?.exclude || [];
|
||||
|
||||
if (includeFiles.length === 0 && excludeFiles.length === 0) {
|
||||
debug('No g3Status include or exclude lists are defined in the angular robot configuration,');
|
||||
debug('skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
/** Random prefix to create unique branch names. */
|
||||
const randomPrefix = `prefix${Math.floor(Math.random() * 1000000)}`;
|
||||
/** Ref name of the temporary master branch. */
|
||||
const masterRef = `${randomPrefix}-master`;
|
||||
/** Ref name of the temporary g3 branch. */
|
||||
const g3Ref = `${randomPrefix}-g3`;
|
||||
/** Url of the ref for fetching master and g3 branches. */
|
||||
const refUrl = `https://github.com/${git.remoteConfig.owner}/${git.remoteConfig.name}.git`;
|
||||
/** The result fo the fetch command. */
|
||||
const fetchResult = git.runGraceful(['fetch', refUrl, `master:${masterRef}`, `g3:${g3Ref}`]);
|
||||
|
||||
// If the upstream repository does not have a g3 branch to compare to, skip the comparison.
|
||||
if (fetchResult.status !== 0) {
|
||||
if (fetchResult.stderr.includes(`couldn't find remote ref g3`)) {
|
||||
return debug('No g3 branch exists on upstream, skipping.');
|
||||
}
|
||||
throw Error('Fetch of master and g3 branches for comparison failed.');
|
||||
}
|
||||
|
||||
/** The statistical information about the git diff between master and g3. */
|
||||
const stats = getDiffStats(git);
|
||||
|
||||
// Delete the temporarily created mater and g3 branches.
|
||||
git.runGraceful(['branch', '-D', masterRef, g3Ref]);
|
||||
|
||||
info.group(bold('g3 branch check'));
|
||||
info(`${stats.commits} commits between g3 and master`);
|
||||
if (stats.files === 0) {
|
||||
info('✅ No sync is needed at this time');
|
||||
} else {
|
||||
info(`${stats.files} files changed, ${stats.insertions} insertions(+), ${
|
||||
stats.deletions} deletions(-) will be included in the next sync`);
|
||||
}
|
||||
info.groupEnd();
|
||||
info();
|
||||
|
||||
|
||||
/**
|
||||
* Get git diff stats between master and g3, for all files and filtered to only g3 affecting
|
||||
* files.
|
||||
*/
|
||||
function getDiffStats(git: GitClient) {
|
||||
/** The diff stats to be returned. */
|
||||
const stats = {
|
||||
insertions: 0,
|
||||
deletions: 0,
|
||||
files: 0,
|
||||
commits: 0,
|
||||
};
|
||||
|
||||
|
||||
// Determine the number of commits between master and g3 refs. */
|
||||
stats.commits = parseInt(git.run(['rev-list', '--count', `${g3Ref}..${masterRef}`]).stdout, 10);
|
||||
|
||||
// Get the numstat information between master and g3
|
||||
git.run(['diff', `${g3Ref}...${masterRef}`, '--numstat'])
|
||||
.stdout
|
||||
// Remove the extra space after git's output.
|
||||
.trim()
|
||||
// Split each line of git output into array
|
||||
.split('\n')
|
||||
// Split each line from the git output into components parts: insertions,
|
||||
// deletions and file name respectively
|
||||
.map(line => line.split('\t'))
|
||||
// Parse number value from the insertions and deletions values
|
||||
// Example raw line input:
|
||||
// 10\t5\tsrc/file/name.ts
|
||||
.map(line => [Number(line[0]), Number(line[1]), line[2]] as [number, number, string])
|
||||
// Add each line's value to the diff stats, and conditionally to the g3
|
||||
// stats as well if the file name is included in the files synced to g3.
|
||||
.forEach(([insertions, deletions, fileName]) => {
|
||||
if (checkMatchAgainstIncludeAndExclude(fileName, includeFiles, excludeFiles)) {
|
||||
stats.insertions += insertions;
|
||||
stats.deletions += deletions;
|
||||
stats.files += 1;
|
||||
}
|
||||
});
|
||||
return stats;
|
||||
}
|
||||
|
||||
/** Determine whether the file name passes both include and exclude checks. */
|
||||
function checkMatchAgainstIncludeAndExclude(
|
||||
file: string, includes: string[], excludes: string[]) {
|
||||
return multimatch(multimatch(file, includes), excludes, {flipNegate: true}).length !== 0;
|
||||
}
|
||||
}
|
56
dev-infra/caretaker/check/github.ts
Normal file
56
dev-infra/caretaker/check/github.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {alias, params, types} from 'typed-graphqlify';
|
||||
|
||||
import {bold, debug, info} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {CaretakerConfig} from '../config';
|
||||
|
||||
|
||||
interface GithubInfoQuery {
|
||||
[key: string]: {
|
||||
issueCount: number,
|
||||
};
|
||||
}
|
||||
|
||||
/** Retrieve the number of matching issues for each github query. */
|
||||
export async function printGithubTasks(git: GitClient, config: CaretakerConfig) {
|
||||
if (!config.githubQueries?.length) {
|
||||
debug('No github queries defined in the configuration, skipping.');
|
||||
return;
|
||||
}
|
||||
info.group(bold('Github Tasks'));
|
||||
await getGithubInfo(git, config);
|
||||
info.groupEnd();
|
||||
info();
|
||||
}
|
||||
|
||||
/** Retrieve query match counts and log discovered counts to the console. */
|
||||
async function getGithubInfo(git: GitClient, {githubQueries: queries = []}: CaretakerConfig) {
|
||||
/** The query object for graphql. */
|
||||
const graphQlQuery: {[key: string]: {issueCount: number}} = {};
|
||||
/** The Github search filter for the configured repository. */
|
||||
const repoFilter = `repo:${git.remoteConfig.owner}/${git.remoteConfig.name}`;
|
||||
queries.forEach(({name, query}) => {
|
||||
/** The name of the query, with spaces removed to match GraphQL requirements. */
|
||||
const queryKey = alias(name.replace(/ /g, ''), 'search');
|
||||
graphQlQuery[queryKey] = params(
|
||||
{
|
||||
type: 'ISSUE',
|
||||
query: `"${repoFilter} ${query.replace(/"/g, '\\"')}"`,
|
||||
},
|
||||
{issueCount: types.number},
|
||||
);
|
||||
});
|
||||
/** The results of the generated github query. */
|
||||
const results = await git.github.graphql.query(graphQlQuery);
|
||||
Object.values(results).forEach((result, i) => {
|
||||
info(`${queries[i]?.name.padEnd(25)} ${result.issueCount}`);
|
||||
});
|
||||
}
|
79
dev-infra/caretaker/check/services.ts
Normal file
79
dev-infra/caretaker/check/services.ts
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 fetch from 'node-fetch';
|
||||
|
||||
import {bold, green, info, red} from '../../utils/console';
|
||||
|
||||
/** The status levels for services. */
|
||||
enum ServiceStatus {
|
||||
GREEN,
|
||||
RED
|
||||
}
|
||||
|
||||
/** The results of checking the status of a service */
|
||||
interface StatusCheckResult {
|
||||
status: ServiceStatus;
|
||||
description: string;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
/** Retrieve and log stasuses for all of the services of concern. */
|
||||
export async function printServiceStatuses() {
|
||||
info.group(bold(`Service Statuses (checked: ${new Date().toLocaleString()})`));
|
||||
logStatus('CircleCI', await getCircleCiStatus());
|
||||
logStatus('Github', await getGithubStatus());
|
||||
logStatus('NPM', await getNpmStatus());
|
||||
logStatus('Saucelabs', await getSaucelabsStatus());
|
||||
info.groupEnd();
|
||||
info();
|
||||
}
|
||||
|
||||
|
||||
/** Log the status of the service to the console. */
|
||||
function logStatus(serviceName: string, status: StatusCheckResult) {
|
||||
serviceName = serviceName.padEnd(15);
|
||||
if (status.status === ServiceStatus.GREEN) {
|
||||
info(`${serviceName} ${green('✅')}`);
|
||||
} else if (status.status === ServiceStatus.RED) {
|
||||
info.group(`${serviceName} ${red('❌')} (Updated: ${status.lastUpdated.toLocaleString()})`);
|
||||
info(` Details: ${status.description}`);
|
||||
info.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the service status information for Saucelabs. */
|
||||
async function getSaucelabsStatus(): Promise<StatusCheckResult> {
|
||||
return getStatusFromStandardApi('https://status.us-west-1.saucelabs.com/api/v2/status.json');
|
||||
}
|
||||
|
||||
/** Gets the service status information for NPM. */
|
||||
async function getNpmStatus(): Promise<StatusCheckResult> {
|
||||
return getStatusFromStandardApi('https://status.npmjs.org/api/v2/status.json');
|
||||
}
|
||||
|
||||
/** Gets the service status information for CircleCI. */
|
||||
async function getCircleCiStatus(): Promise<StatusCheckResult> {
|
||||
return getStatusFromStandardApi('https://status.circleci.com/api/v2/status.json');
|
||||
}
|
||||
|
||||
/** Gets the service status information for Github. */
|
||||
async function getGithubStatus(): Promise<StatusCheckResult> {
|
||||
return getStatusFromStandardApi('https://www.githubstatus.com/api/v2/status.json');
|
||||
}
|
||||
|
||||
/** Retrieve the status information for a service which uses a standard API response. */
|
||||
async function getStatusFromStandardApi(url: string) {
|
||||
const result = await fetch(url).then(result => result.json());
|
||||
const status = result.status.indicator === 'none' ? ServiceStatus.GREEN : ServiceStatus.RED;
|
||||
return {
|
||||
status,
|
||||
description: result.status.description,
|
||||
lastUpdated: new Date(result.page.updated_at)
|
||||
};
|
||||
}
|
16
dev-infra/caretaker/cli.ts
Normal file
16
dev-infra/caretaker/cli.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {Argv} from 'yargs';
|
||||
import {CheckModule} from './check/cli';
|
||||
|
||||
|
||||
/** Build the parser for the caretaker commands. */
|
||||
export function buildCaretakerParser(yargs: Argv) {
|
||||
return yargs.command(CheckModule);
|
||||
}
|
24
dev-infra/caretaker/config.ts
Normal file
24
dev-infra/caretaker/config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {assertNoErrors, getConfig, NgDevConfig} from '../utils/config';
|
||||
|
||||
export interface CaretakerConfig {
|
||||
githubQueries?: {name: string; query: string;}[];
|
||||
}
|
||||
|
||||
/** Retrieve and validate the config as `CaretakerConfig`. */
|
||||
export function getCaretakerConfig() {
|
||||
// List of errors encountered validating the config.
|
||||
const errors: string[] = [];
|
||||
// The non-validated config object.
|
||||
const config: Partial<NgDevConfig<{caretaker: CaretakerConfig}>> = getConfig();
|
||||
|
||||
assertNoErrors(errors);
|
||||
return config as Required<typeof config>;
|
||||
}
|
@ -13,8 +13,11 @@ import {buildCommitMessageParser} from './commit-message/cli';
|
||||
import {buildFormatParser} from './format/cli';
|
||||
import {buildReleaseParser} from './release/cli';
|
||||
import {buildPrParser} from './pr/cli';
|
||||
import {captureLogOutputForCommand} from './utils/console';
|
||||
import {buildCaretakerParser} from './caretaker/cli';
|
||||
|
||||
yargs.scriptName('ng-dev')
|
||||
.middleware(captureLogOutputForCommand)
|
||||
.demandCommand()
|
||||
.recommendCommands()
|
||||
.command('commit-message <command>', '', buildCommitMessageParser)
|
||||
@ -23,6 +26,7 @@ yargs.scriptName('ng-dev')
|
||||
.command('pullapprove <command>', '', buildPullapproveParser)
|
||||
.command('release <command>', '', buildReleaseParser)
|
||||
.command('ts-circular-deps <command>', '', tsCircularDependenciesBuilder)
|
||||
.command('caretaker <command>', '', buildCaretakerParser)
|
||||
.wrap(120)
|
||||
.strict()
|
||||
.parse();
|
||||
|
@ -49,7 +49,7 @@ export const COMMIT_TYPES: {[key: string]: CommitType} = {
|
||||
build: {
|
||||
name: 'build',
|
||||
description: 'Changes to local repository build system and tooling',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
scope: ScopeRequirement.Optional,
|
||||
},
|
||||
ci: {
|
||||
name: 'ci',
|
||||
|
@ -6,9 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {info} from 'console';
|
||||
import {writeFileSync} from 'fs';
|
||||
|
||||
import {debug, log} from '../utils/console';
|
||||
|
||||
import {loadCommitMessageDraft} from './commit-message-draft';
|
||||
|
||||
/**
|
||||
@ -20,19 +21,19 @@ import {loadCommitMessageDraft} from './commit-message-draft';
|
||||
export function restoreCommitMessage(
|
||||
filePath: string, source?: 'message'|'template'|'squash'|'commit') {
|
||||
if (!!source) {
|
||||
info('Skipping commit message restoration attempt');
|
||||
log('Skipping commit message restoration attempt');
|
||||
if (source === 'message') {
|
||||
info('A commit message was already provided via the command with a -m or -F flag');
|
||||
debug('A commit message was already provided via the command with a -m or -F flag');
|
||||
}
|
||||
if (source === 'template') {
|
||||
info('A commit message was already provided via the -t flag or config.template setting');
|
||||
debug('A commit message was already provided via the -t flag or config.template setting');
|
||||
}
|
||||
if (source === 'squash') {
|
||||
info('A commit message was already provided as a merge action or via .git/MERGE_MSG');
|
||||
debug('A commit message was already provided as a merge action or via .git/MERGE_MSG');
|
||||
}
|
||||
if (source === 'commit') {
|
||||
info('A commit message was already provided through a revision specified via --fixup, -c,');
|
||||
info('-C or --amend flag');
|
||||
debug('A commit message was already provided through a revision specified via --fixup, -c,');
|
||||
debug('-C or --amend flag');
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
@ -8,35 +8,21 @@
|
||||
|
||||
import {Arguments, Argv, CommandModule} from 'yargs';
|
||||
|
||||
import {error} from '../../utils/console';
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
import {checkOutPullRequestLocally} from '../common/checkout-pr';
|
||||
|
||||
export interface CheckoutOptions {
|
||||
prNumber: number;
|
||||
'github-token'?: string;
|
||||
githubToken: string;
|
||||
}
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
/** Builds the checkout pull request command. */
|
||||
function builder(yargs: Argv) {
|
||||
return yargs.positional('prNumber', {type: 'number', demandOption: true}).option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
});
|
||||
return addGithubTokenFlag(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
}
|
||||
|
||||
/** Handles the checkout pull request command. */
|
||||
async function handler({prNumber, 'github-token': token}: Arguments<CheckoutOptions>) {
|
||||
const githubToken = token || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error('No Github token set. Please set the `GITHUB_TOKEN` environment variable.');
|
||||
error('Alternatively, pass the `--github-token` command line flag.');
|
||||
error(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
async function handler({prNumber, githubToken}: Arguments<CheckoutOptions>) {
|
||||
const prCheckoutOptions = {allowIfMaintainerCannotModify: true, branchName: `pr-${prNumber}`};
|
||||
await checkOutPullRequestLocally(prNumber, githubToken, prCheckoutOptions);
|
||||
}
|
||||
|
@ -8,36 +8,24 @@
|
||||
|
||||
import {Arguments, Argv} from 'yargs';
|
||||
|
||||
import {error, red, yellow} from '../../utils/console';
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
|
||||
import {GITHUB_TOKEN_GENERATE_URL, mergePullRequest} from './index';
|
||||
import {mergePullRequest} from './index';
|
||||
|
||||
/** The options available to the merge command via CLI. */
|
||||
export interface MergeCommandOptions {
|
||||
'github-token'?: string;
|
||||
githubToken: string;
|
||||
'pr-number': number;
|
||||
}
|
||||
|
||||
/** Builds the options for the merge command. */
|
||||
export function buildMergeCommand(yargs: Argv): Argv<MergeCommandOptions> {
|
||||
return yargs.help()
|
||||
.strict()
|
||||
.positional('pr-number', {demandOption: true, type: 'number'})
|
||||
.option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
});
|
||||
return addGithubTokenFlag(yargs).help().strict().positional(
|
||||
'pr-number', {demandOption: true, type: 'number'});
|
||||
}
|
||||
|
||||
/** Handles the merge command. i.e. performs the merge of a specified pull request. */
|
||||
export async function handleMergeCommand(args: Arguments<MergeCommandOptions>) {
|
||||
const githubToken = args['github-token'] || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error(red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
|
||||
error(red('Alternatively, pass the `--github-token` command line flag.'));
|
||||
error(yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await mergePullRequest(args['pr-number'], githubToken);
|
||||
export async function handleMergeCommand(
|
||||
{'pr-number': pr, githubToken}: Arguments<MergeCommandOptions>) {
|
||||
await mergePullRequest(pr, githubToken);
|
||||
}
|
||||
|
@ -165,6 +165,11 @@ export async function findActiveVersionBranches(
|
||||
latestVersionBranch: string | null,
|
||||
releaseCandidateBranch: string | null,
|
||||
}> {
|
||||
// Version representing the release-train currently in the next phase. Note that we ignore
|
||||
// patch and pre-release segments in order to be able to compare the next release train to
|
||||
// other release trains from version branches (which follow the `N.N.x` pattern).
|
||||
const nextReleaseTrainVersion = semver.parse(`${nextVersion.major}.${nextVersion.minor}.0`)!;
|
||||
|
||||
let latestVersionBranch: string|null = null;
|
||||
let releaseCandidateBranch: string|null = null;
|
||||
|
||||
@ -177,15 +182,21 @@ export async function findActiveVersionBranches(
|
||||
// next version-branch as that one is supposed to be the latest active version-branch. If it
|
||||
// is not, then an error will be thrown due to two FF/RC branches existing at the same time.
|
||||
for (const {name, parsed} of branches) {
|
||||
// It can happen that version branches that are more recent than the version in the next
|
||||
// branch (i.e. `master`) have been created. We could ignore such branches silently, but
|
||||
// it might actually be symptomatic for an outdated version in the `next` branch, or an
|
||||
// It can happen that version branches have been accidentally created which are more recent
|
||||
// than the release-train in the next branch (i.e. `master`). We could ignore such branches
|
||||
// silently, but it might be symptomatic for an outdated version in the `next` branch, or an
|
||||
// accidentally created branch by the caretaker. In either way we want to raise awareness.
|
||||
if (semver.gte(parsed, nextVersion)) {
|
||||
if (semver.gt(parsed, nextReleaseTrainVersion)) {
|
||||
throw Error(
|
||||
`Discovered unexpected version-branch that is representing a minor ` +
|
||||
`version more recent than the one in the "${nextBranchName}" branch. Consider ` +
|
||||
`deleting the branch, or check if the version in "${nextBranchName}" is outdated.`);
|
||||
`Discovered unexpected version-branch "${name}" for a release-train that is ` +
|
||||
`more recent than the release-train currently in the "${nextBranchName}" branch. ` +
|
||||
`Please either delete the branch if created by accident, or update the outdated ` +
|
||||
`version in the next branch (${nextBranchName}).`);
|
||||
} else if (semver.eq(parsed, nextReleaseTrainVersion)) {
|
||||
throw Error(
|
||||
`Discovered unexpected version-branch "${name}" for a release-train that is already ` +
|
||||
`active in the "${nextBranchName}" branch. Please either delete the branch if ` +
|
||||
`created by accident, or update the version in the next branch (${nextBranchName}).`);
|
||||
}
|
||||
|
||||
const version = await getVersionOfBranch(repo, name);
|
||||
|
@ -280,7 +280,21 @@ describe('default target labels', () => {
|
||||
.toBeRejectedWithError('Invalid version detected in following branch: 11.1.x.');
|
||||
});
|
||||
|
||||
it('should error if branch more recent than version in "next" branch is found', async () => {
|
||||
it('should error if version-branch more recent than "next" is discovered', async () => {
|
||||
interceptBranchVersionRequest('master', '11.2.0-next.0');
|
||||
interceptBranchVersionRequest('11.3.x', '11.3.0-next.0');
|
||||
interceptBranchVersionRequest('11.1.x', '11.1.5');
|
||||
interceptBranchesListRequest(['11.1.x', '11.3.x']);
|
||||
|
||||
await expectAsync(getBranchesForLabel('target: lts', '10.2.x'))
|
||||
.toBeRejectedWithError(
|
||||
'Discovered unexpected version-branch "11.3.x" for a release-train that is ' +
|
||||
'more recent than the release-train currently in the "master" branch. Please ' +
|
||||
'either delete the branch if created by accident, or update the outdated version ' +
|
||||
'in the next branch (master).');
|
||||
});
|
||||
|
||||
it('should error if branch is matching with release-train in the "next" branch', async () => {
|
||||
interceptBranchVersionRequest('master', '11.2.0-next.0');
|
||||
interceptBranchVersionRequest('11.2.x', '11.2.0-next.0');
|
||||
interceptBranchVersionRequest('11.1.x', '11.1.5');
|
||||
@ -288,9 +302,9 @@ describe('default target labels', () => {
|
||||
|
||||
await expectAsync(getBranchesForLabel('target: lts', '10.2.x'))
|
||||
.toBeRejectedWithError(
|
||||
'Discovered unexpected version-branch that is representing a minor version more ' +
|
||||
'recent than the one in the "master" branch. Consider deleting the branch, or check ' +
|
||||
'if the version in "master" is outdated.');
|
||||
'Discovered unexpected version-branch "11.2.x" for a release-train that is already ' +
|
||||
'active in the "master" branch. Please either delete the branch if created by ' +
|
||||
'accident, or update the version in the next branch (master).');
|
||||
});
|
||||
|
||||
it('should allow merging PR only into patch branch with "target: patch"', async () => {
|
||||
|
@ -11,13 +11,11 @@ import {getConfig, getRepoBaseDir} from '../../utils/config';
|
||||
import {error, green, info, promptConfirm, red, yellow} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {GithubApiRequestError} from '../../utils/git/github';
|
||||
import {GITHUB_TOKEN_GENERATE_URL} from '../../utils/yargs';
|
||||
|
||||
import {loadAndValidateConfig, MergeConfig, MergeConfigWithRemote} from './config';
|
||||
import {loadAndValidateConfig, MergeConfigWithRemote} from './config';
|
||||
import {MergeResult, MergeStatus, PullRequestMergeTask} from './task';
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
|
||||
/**
|
||||
* Merges a given pull request based on labels configured in the given merge configuration.
|
||||
|
@ -8,39 +8,23 @@
|
||||
|
||||
import {Arguments, Argv} from 'yargs';
|
||||
|
||||
import {error} from '../../utils/console';
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
|
||||
import {rebasePr} from './index';
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
/** The options available to the rebase command via CLI. */
|
||||
export interface RebaseCommandOptions {
|
||||
'github-token'?: string;
|
||||
githubToken: string;
|
||||
prNumber: number;
|
||||
}
|
||||
|
||||
/** Builds the rebase pull request command. */
|
||||
export function buildRebaseCommand(yargs: Argv): Argv<RebaseCommandOptions> {
|
||||
return yargs
|
||||
.option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
})
|
||||
.positional('prNumber', {type: 'number', demandOption: true});
|
||||
return addGithubTokenFlag(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
}
|
||||
|
||||
|
||||
/** Handles the rebase pull request command. */
|
||||
export async function handleRebaseCommand(args: Arguments<RebaseCommandOptions>) {
|
||||
const githubToken = args['github-token'] || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error('No Github token set. Please set the `GITHUB_TOKEN` environment variable.');
|
||||
error('Alternatively, pass the `--github-token` command line flag.');
|
||||
error(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await rebasePr(args.prNumber, githubToken);
|
||||
export async function handleRebaseCommand(
|
||||
{prNumber, githubToken}: Arguments<RebaseCommandOptions>) {
|
||||
await rebasePr(prNumber, githubToken);
|
||||
}
|
||||
|
@ -12,14 +12,18 @@ ts_library(
|
||||
"@npm//@octokit/graphql",
|
||||
"@npm//@octokit/rest",
|
||||
"@npm//@octokit/types",
|
||||
"@npm//@types/fs-extra",
|
||||
"@npm//@types/inquirer",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//@types/yargs",
|
||||
"@npm//chalk",
|
||||
"@npm//fs-extra",
|
||||
"@npm//inquirer",
|
||||
"@npm//inquirer-autocomplete-prompt",
|
||||
"@npm//shelljs",
|
||||
"@npm//tslib",
|
||||
"@npm//typed-graphqlify",
|
||||
"@npm//yargs",
|
||||
],
|
||||
)
|
||||
|
@ -7,14 +7,19 @@
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {writeFileSync} from 'fs-extra';
|
||||
import {createPromptModule, ListChoiceOptions, prompt} from 'inquirer';
|
||||
import * as inquirerAutocomplete from 'inquirer-autocomplete-prompt';
|
||||
import {join} from 'path';
|
||||
import {Arguments} from 'yargs';
|
||||
|
||||
import {getRepoBaseDir} from './config';
|
||||
|
||||
/** Reexport of chalk colors for convenient access. */
|
||||
export const red: typeof chalk = chalk.red;
|
||||
export const green: typeof chalk = chalk.green;
|
||||
export const yellow: typeof chalk = chalk.yellow;
|
||||
export const bold: typeof chalk = chalk.bold;
|
||||
|
||||
/** Prompts the user with a confirmation question and a specified message. */
|
||||
export async function promptConfirm(message: string, defaultValue = false): Promise<boolean> {
|
||||
@ -140,6 +145,7 @@ function runConsoleCommand(loadCommand: () => Function, logLevel: LOG_LEVELS, ..
|
||||
if (getLogLevel() >= logLevel) {
|
||||
loadCommand()(...text);
|
||||
}
|
||||
printToLogFile(logLevel, ...text);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,3 +161,56 @@ function getLogLevel() {
|
||||
}
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
/** All text to write to the log file. */
|
||||
let LOGGED_TEXT = '';
|
||||
/** Whether file logging as been enabled. */
|
||||
let FILE_LOGGING_ENABLED = false;
|
||||
/**
|
||||
* The number of columns used in the prepended log level information on each line of the logging
|
||||
* output file.
|
||||
*/
|
||||
const LOG_LEVEL_COLUMNS = 7;
|
||||
|
||||
/**
|
||||
* Enable writing the logged outputs to the log file on process exit, sets initial lines from the
|
||||
* command execution, containing information about the timing and command parameters.
|
||||
*
|
||||
* This is expected to be called only once during a command run, and should be called by the
|
||||
* middleware of yargs to enable the file logging before the rest of the command parsing and
|
||||
* response is executed.
|
||||
*/
|
||||
export function captureLogOutputForCommand(argv: Arguments) {
|
||||
if (FILE_LOGGING_ENABLED) {
|
||||
throw Error('`captureLogOutputForCommand` cannot be called multiple times');
|
||||
}
|
||||
/** The date time used for timestamping when the command was invoked. */
|
||||
const now = new Date();
|
||||
/** Header line to separate command runs in log files. */
|
||||
const headerLine = Array(100).fill('#').join('');
|
||||
LOGGED_TEXT += `${headerLine}\nCommand: ${argv.$0} ${argv._.join(' ')}\nRan at: ${now}\n`;
|
||||
|
||||
// On process exit, write the logged output to the appropriate log files
|
||||
process.on('exit', (code: number) => {
|
||||
LOGGED_TEXT += `Command ran in ${new Date().getTime() - now.getTime()}ms`;
|
||||
/** Path to the log file location. */
|
||||
const logFilePath = join(getRepoBaseDir(), '.ng-dev.log');
|
||||
|
||||
writeFileSync(logFilePath, LOGGED_TEXT);
|
||||
|
||||
// For failure codes greater than 1, the new logged lines should be written to a specific log
|
||||
// file for the command run failure.
|
||||
if (code > 1) {
|
||||
writeFileSync(join(getRepoBaseDir(), `.ng-dev.err-${now.getTime()}.log`), LOGGED_TEXT);
|
||||
}
|
||||
});
|
||||
|
||||
// Mark file logging as enabled to prevent the function from executing multiple times.
|
||||
FILE_LOGGING_ENABLED = true;
|
||||
}
|
||||
|
||||
/** Write the provided text to the log file, prepending each line with the log level. */
|
||||
function printToLogFile(logLevel: LOG_LEVELS, ...text: string[]) {
|
||||
const logLevelText = `${LOG_LEVELS[logLevel]}:`.padEnd(LOG_LEVEL_COLUMNS);
|
||||
LOGGED_TEXT += text.join(' ').split('\n').map(l => `${logLevelText} ${l}\n`).join('');
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export class GitCommandError extends Error {
|
||||
/**
|
||||
* Common client for performing Git interactions.
|
||||
*
|
||||
* Takes in two optional arguements:
|
||||
* Takes in two optional arguments:
|
||||
* _githubToken: the token used for authentifation in github interactions, by default empty
|
||||
* allowing readonly actions.
|
||||
* _config: The dev-infra configuration containing GitClientConfig information, by default
|
||||
@ -86,10 +86,10 @@ export class GitClient {
|
||||
/**
|
||||
* Spawns a given Git command process. Does not throw if the command fails. Additionally,
|
||||
* if there is any stderr output, the output will be printed. This makes it easier to
|
||||
* debug failed commands.
|
||||
* info failed commands.
|
||||
*/
|
||||
runGraceful(args: string[], options: SpawnSyncOptions = {}): SpawnSyncReturns<string> {
|
||||
// To improve the debugging experience in case something fails, we print all executed
|
||||
// To improve the infoging experience in case something fails, we print all executed
|
||||
// Git commands. Note that we do not want to print the token if is contained in the
|
||||
// command. It's common to share errors with others if the tool failed.
|
||||
info('Executing: git', this.omitGithubTokenFromMessage(args.join(' ')));
|
||||
|
37
dev-infra/utils/yargs.ts
Normal file
37
dev-infra/utils/yargs.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {Argv} from 'yargs';
|
||||
import {error, red, yellow} from './console';
|
||||
|
||||
export type ArgvWithGithubToken = Argv<{githubToken: string}>;
|
||||
|
||||
export function addGithubTokenFlag(yargs: Argv): ArgvWithGithubToken {
|
||||
return yargs
|
||||
// 'github-token' is casted to 'githubToken' to properly set up typings to reflect the key in
|
||||
// the Argv object being camelCase rather than kebob case due to the `camel-case-expansion`
|
||||
// config: https://github.com/yargs/yargs-parser#camel-case-expansion
|
||||
.option('github-token' as 'githubToken', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.',
|
||||
coerce: (token: string) => {
|
||||
const githubToken = token || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error(red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
|
||||
error(red('Alternatively, pass the `--github-token` command line flag.'));
|
||||
error(yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
|
||||
process.exit(1);
|
||||
}
|
||||
return githubToken;
|
||||
},
|
||||
})
|
||||
.default('github-token' as 'githubToken', '', '<LOCAL TOKEN>');
|
||||
}
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = 'https://github.com/settings/tokens/new';
|
@ -19,6 +19,7 @@ export declare enum ErrorCode {
|
||||
CONFIG_FLAT_MODULE_NO_INDEX = 4001,
|
||||
CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK = 4002,
|
||||
HOST_BINDING_PARSE_ERROR = 5001,
|
||||
TEMPLATE_PARSE_ERROR = 5002,
|
||||
NGMODULE_INVALID_DECLARATION = 6001,
|
||||
NGMODULE_INVALID_IMPORT = 6002,
|
||||
NGMODULE_INVALID_EXPORT = 6003,
|
||||
|
2
goldens/public-api/router/router.d.ts
vendored
2
goldens/public-api/router/router.d.ts
vendored
@ -432,7 +432,7 @@ export declare class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||
constructor(router: Router, route: ActivatedRoute, locationStrategy: LocationStrategy);
|
||||
ngOnChanges(changes: SimpleChanges): any;
|
||||
ngOnDestroy(): any;
|
||||
onClick(button: number, ctrlKey: boolean, metaKey: boolean, shiftKey: boolean): boolean;
|
||||
onClick(button: number, ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean): boolean;
|
||||
}
|
||||
|
||||
export declare class RouterModule {
|
||||
|
@ -21,7 +21,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 147573,
|
||||
"main-es2015": 146989,
|
||||
"polyfills-es2015": 36571
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,6 @@ export class AppComponent {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
time$: Observable<string> =
|
||||
this.http.get('http://worldclockapi.com/api/json/pst/now')
|
||||
.pipe(map((result: any) => result.currentDateTime), startWith(['...']));
|
||||
this.http.get('http://worldtimeapi.org/api/timezone/America/Los_Angeles.json')
|
||||
.pipe(map((result: any) => result.datetime), startWith(['...']));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "10.1.0-rc.0",
|
||||
"version": "10.1.1",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
@ -81,13 +81,14 @@
|
||||
"@types/jasmine-ajax": "^3.3.1",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/multimatch": "^4.0.0",
|
||||
"@types/node": "^12.11.1",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/selenium-webdriver": "3.0.7",
|
||||
"@types/semver": "^6.0.2",
|
||||
"@types/shelljs": "^0.8.6",
|
||||
"@types/systemjs": "0.19.32",
|
||||
"@types/yaml": "^1.2.0",
|
||||
"@types/yaml": "^1.9.7",
|
||||
"@types/yargs": "^15.0.5",
|
||||
"@webcomponents/custom-elements": "^1.1.0",
|
||||
"angular": "npm:angular@1.7",
|
||||
@ -152,7 +153,7 @@
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "~4.0.2",
|
||||
"xhr2": "0.2.0",
|
||||
"yaml": "^1.7.2",
|
||||
"yaml": "^1.10.0",
|
||||
"yargs": "^15.4.1"
|
||||
},
|
||||
"// 2": "devDependencies are not used under Bazel. Many can be removed after test.sh is deleted.",
|
||||
|
@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {absoluteFromSourceFile} from '../../../src/ngtsc/file_system';
|
||||
|
||||
import {absoluteFromSourceFile} from '../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../../../src/ngtsc/logging';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference, TypeValueReferenceKind, ValueUnavailableKind} from '../../../src/ngtsc/reflection';
|
||||
import {isWithinPackage} from '../analysis/util';
|
||||
@ -1593,35 +1593,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
constructorParamInfo[index] :
|
||||
{decorators: null, typeExpression: null};
|
||||
const nameNode = node.name;
|
||||
|
||||
let typeValueReference: TypeValueReference;
|
||||
if (typeExpression !== null) {
|
||||
// `typeExpression` is an expression in a "type" context. Resolve it to a declared value.
|
||||
// Either it's a reference to an imported type, or a type declared locally. Distinguish the
|
||||
// two cases with `getDeclarationOfExpression`.
|
||||
const decl = this.getDeclarationOfExpression(typeExpression);
|
||||
if (decl !== null && decl.node !== null && decl.viaModule !== null &&
|
||||
isNamedDeclaration(decl.node)) {
|
||||
typeValueReference = {
|
||||
kind: TypeValueReferenceKind.IMPORTED,
|
||||
valueDeclaration: decl.node,
|
||||
moduleName: decl.viaModule,
|
||||
importedName: decl.node.name.text,
|
||||
nestedPath: null,
|
||||
};
|
||||
} else {
|
||||
typeValueReference = {
|
||||
kind: TypeValueReferenceKind.LOCAL,
|
||||
expression: typeExpression,
|
||||
defaultImportStatement: null,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
typeValueReference = {
|
||||
kind: TypeValueReferenceKind.UNAVAILABLE,
|
||||
reason: {kind: ValueUnavailableKind.MISSING_TYPE},
|
||||
};
|
||||
}
|
||||
const typeValueReference = this.typeToValue(typeExpression);
|
||||
|
||||
return {
|
||||
name: getNameText(nameNode),
|
||||
@ -1633,6 +1605,29 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the `TypeValueReference` for the given `typeExpression`.
|
||||
*
|
||||
* In ngcc, all the `typeExpression` are guaranteed to be "values" because it is working in JS and
|
||||
* not TS. This means that the TS compiler is not going to remove the "type" import and so we can
|
||||
* always use a LOCAL `TypeValueReference` kind, rather than trying to force an additional import
|
||||
* for non-local expressions.
|
||||
*/
|
||||
private typeToValue(typeExpression: ts.Expression|null): TypeValueReference {
|
||||
if (typeExpression === null) {
|
||||
return {
|
||||
kind: TypeValueReferenceKind.UNAVAILABLE,
|
||||
reason: {kind: ValueUnavailableKind.MISSING_TYPE},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind: TypeValueReferenceKind.LOCAL,
|
||||
expression: typeExpression,
|
||||
defaultImportStatement: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameter type and decorators for the constructor of a class,
|
||||
* where the information is stored on a static property of the class.
|
||||
|
@ -1211,6 +1211,69 @@ exports.MissingClass2 = MissingClass2;
|
||||
});
|
||||
|
||||
describe('getConstructorParameters', () => {
|
||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
||||
() => {
|
||||
const files = [
|
||||
{
|
||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
||||
contents: `
|
||||
declare class Foo {}
|
||||
export {Foo as Bar};
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
||||
contents: `
|
||||
export {Bar as Baz} from './foo';
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/local.js'),
|
||||
contents: `
|
||||
var Internal = (function() {
|
||||
function Internal() {
|
||||
}
|
||||
return Internal;
|
||||
}());
|
||||
exports.External = Internal;
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/main.js'),
|
||||
contents: `
|
||||
var shared = require('shared-lib');
|
||||
var local = require('./local');
|
||||
var SameFile = (function() {
|
||||
function SameFile() {
|
||||
}
|
||||
return SameFile;
|
||||
}());
|
||||
exports.SameFile = SameFile;
|
||||
|
||||
var SomeClass = (function() {
|
||||
function SomeClass(arg1, arg2, arg3) {}
|
||||
return SomeClass;
|
||||
}());
|
||||
SomeClass.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; };
|
||||
exports.SomeClass = SomeClass;
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
loadTestFiles(files);
|
||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
||||
const host =
|
||||
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
||||
const classNode = getDeclaration(
|
||||
bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration);
|
||||
|
||||
const parameters = host.getConstructorParameters(classNode)!;
|
||||
|
||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
||||
expectTypeValueReferencesForParameters(
|
||||
parameters, ['shared.Baz', 'local.External', 'SameFile']);
|
||||
});
|
||||
|
||||
it('should find the decorated constructor parameters', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
|
@ -1140,6 +1140,57 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
describe('getConstructorParameters()', () => {
|
||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
||||
() => {
|
||||
const files = [
|
||||
{
|
||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
||||
contents: `
|
||||
declare class Foo {}
|
||||
export {Foo as Bar};
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
||||
contents: `
|
||||
export {Bar as Baz} from './foo';
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/local.js'),
|
||||
contents: `
|
||||
class Internal {}
|
||||
export {Internal as External};
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/main.js'),
|
||||
contents: `
|
||||
import {Baz} from 'shared-lib';
|
||||
import {External} from './local';
|
||||
export class SameFile {}
|
||||
|
||||
export class SomeClass {
|
||||
constructor(arg1, arg2, arg3) {}
|
||||
}
|
||||
SomeClass.ctorParameters = [{ type: Baz }, { type: External }, { type: SameFile }];
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
loadTestFiles(files);
|
||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const classNode =
|
||||
getDeclaration(bundle.program, _('/main.js'), 'SomeClass', isNamedClassDeclaration);
|
||||
|
||||
const parameters = host.getConstructorParameters(classNode)!;
|
||||
|
||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
||||
expectTypeValueReferencesForParameters(parameters, ['Baz', 'External', 'SameFile']);
|
||||
});
|
||||
|
||||
it('should find the decorated constructor parameters', () => {
|
||||
loadFakeCore(getFileSystem());
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
@ -1154,7 +1205,7 @@ runInEachFileSystem(() => {
|
||||
'_viewContainer', '_template', 'injected'
|
||||
]);
|
||||
expectTypeValueReferencesForParameters(
|
||||
parameters, ['ViewContainerRef', 'TemplateRef', null], '@angular/core');
|
||||
parameters, ['ViewContainerRef', 'TemplateRef', null]);
|
||||
});
|
||||
|
||||
it('should accept `ctorParameters` as an array', () => {
|
||||
|
@ -1252,6 +1252,67 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
describe('getConstructorParameters()', () => {
|
||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
||||
() => {
|
||||
const files = [
|
||||
{
|
||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
||||
contents: `
|
||||
declare class Foo {}
|
||||
export {Foo as Bar};
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
||||
contents: `
|
||||
export {Bar as Baz} from './foo';
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/local.js'),
|
||||
contents: `
|
||||
var Internal = (function() {
|
||||
function Internal() {
|
||||
}
|
||||
return Internal;
|
||||
}());
|
||||
export {Internal as External};
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/main.js'),
|
||||
contents: `
|
||||
import {Baz} from 'shared-lib';
|
||||
import {External} from './local';
|
||||
var SameFile = (function() {
|
||||
function SameFile() {
|
||||
}
|
||||
return SameFile;
|
||||
}());
|
||||
export SameFile;
|
||||
|
||||
var SomeClass = (function() {
|
||||
function SomeClass(arg1, arg2, arg3) {}
|
||||
return SomeClass;
|
||||
}());
|
||||
SomeClass.ctorParameters = function() { return [{ type: Baz }, { type: External }, { type: SameFile }]; };
|
||||
export SomeClass;
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
loadTestFiles(files);
|
||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
||||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||
const classNode = getDeclaration(
|
||||
bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration);
|
||||
|
||||
const parameters = host.getConstructorParameters(classNode)!;
|
||||
|
||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
||||
expectTypeValueReferencesForParameters(parameters, ['Baz', 'External', 'SameFile']);
|
||||
});
|
||||
|
||||
it('should find the decorated constructor parameters', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
|
@ -1332,6 +1332,78 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
describe('getConstructorParameters', () => {
|
||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
||||
() => {
|
||||
const files = [
|
||||
{
|
||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
||||
contents: `
|
||||
declare class Foo {}
|
||||
export {Foo as Bar};
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
||||
contents: `
|
||||
export {Bar as Baz} from './foo';
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/local.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define('local', ['exports'], factory) :
|
||||
(factory(global.local));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
var Internal = (function() {
|
||||
function Internal() {
|
||||
}
|
||||
return Internal;
|
||||
}());
|
||||
exports.External = Internal;
|
||||
})));
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/main.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('shared-lib), require('./local')) :
|
||||
typeof define === 'function' && define.amd ? define('main', ['exports', 'shared-lib', './local'], factory) :
|
||||
(factory(global.main, global.shared, global.local));
|
||||
}(this, (function (exports, shared, local) { 'use strict';
|
||||
var SameFile = (function() {
|
||||
function SameFile() {
|
||||
}
|
||||
return SameFile;
|
||||
}());
|
||||
exports.SameFile = SameFile;
|
||||
|
||||
var SomeClass = (function() {
|
||||
function SomeClass(arg1, arg2, arg3) {}
|
||||
return SomeClass;
|
||||
}());
|
||||
SomeClass.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; };
|
||||
exports.SomeClass = SomeClass;
|
||||
})));
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
loadTestFiles(files);
|
||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
||||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||
const classNode = getDeclaration(
|
||||
bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration);
|
||||
|
||||
const parameters = host.getConstructorParameters(classNode)!;
|
||||
|
||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
||||
expectTypeValueReferencesForParameters(
|
||||
parameters, ['shared.Baz', 'local.External', 'SameFile']);
|
||||
});
|
||||
|
||||
it('should find the decorated constructor parameters', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
@ -1591,7 +1663,7 @@ runInEachFileSystem(() => {
|
||||
`;
|
||||
break;
|
||||
case 'inlined_with_suffix':
|
||||
fileHeaderWithUmd = `
|
||||
fileHeaderWithUmd = `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports)) :
|
||||
typeof define === 'function' && define.amd ? define('test', ['exports'], factory) :
|
||||
|
@ -13,26 +13,36 @@ import {CtorParameter, TypeValueReferenceKind} from '../../../src/ngtsc/reflecti
|
||||
* names.
|
||||
*/
|
||||
export function expectTypeValueReferencesForParameters(
|
||||
parameters: CtorParameter[], expectedParams: (string|null)[], fromModule: string|null = null) {
|
||||
parameters: CtorParameter[], expectedParams: (string|null)[],
|
||||
fromModule: (string|null)[] = []) {
|
||||
parameters!.forEach((param, idx) => {
|
||||
const expected = expectedParams[idx];
|
||||
if (expected !== null) {
|
||||
if (param.typeValueReference.kind === TypeValueReferenceKind.UNAVAILABLE) {
|
||||
fail(`Incorrect typeValueReference generated, expected ${expected}`);
|
||||
fail(`Incorrect typeValueReference generated for ${param.name}, expected "${
|
||||
expected}" because "${param.typeValueReference.reason}"`);
|
||||
} else if (
|
||||
param.typeValueReference.kind === TypeValueReferenceKind.LOCAL && fromModule !== null) {
|
||||
fail(`Incorrect typeValueReference generated, expected non-local`);
|
||||
param.typeValueReference.kind === TypeValueReferenceKind.LOCAL &&
|
||||
fromModule[idx] != null) {
|
||||
fail(`Incorrect typeValueReference generated for ${param.name}, expected non-LOCAL (from ${
|
||||
fromModule[idx]}) but was marked LOCAL`);
|
||||
} else if (
|
||||
param.typeValueReference.kind !== TypeValueReferenceKind.LOCAL && fromModule === null) {
|
||||
fail(`Incorrect typeValueReference generated, expected local`);
|
||||
param.typeValueReference.kind !== TypeValueReferenceKind.LOCAL &&
|
||||
fromModule[idx] == null) {
|
||||
fail(`Incorrect typeValueReference generated for ${
|
||||
param.name}, expected LOCAL but was imported from ${
|
||||
param.typeValueReference.moduleName}`);
|
||||
} else if (param.typeValueReference.kind === TypeValueReferenceKind.LOCAL) {
|
||||
if (!ts.isIdentifier(param.typeValueReference.expression)) {
|
||||
fail(`Incorrect typeValueReference generated, expected identifier`);
|
||||
if (!ts.isIdentifier(param.typeValueReference.expression) &&
|
||||
!ts.isPropertyAccessExpression(param.typeValueReference.expression)) {
|
||||
fail(`Incorrect typeValueReference generated for ${
|
||||
param.name}, expected an identifier but got "${
|
||||
param.typeValueReference.expression.getText()}"`);
|
||||
} else {
|
||||
expect(param.typeValueReference.expression.text).toEqual(expected);
|
||||
expect(param.typeValueReference.expression.getText()).toEqual(expected);
|
||||
}
|
||||
} else if (param.typeValueReference.kind === TypeValueReferenceKind.IMPORTED) {
|
||||
expect(param.typeValueReference.moduleName).toBe(fromModule!);
|
||||
expect(param.typeValueReference.moduleName).toBe(fromModule[idx]!);
|
||||
expect(param.typeValueReference.importedName).toBe(expected);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/shims:api",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
|
@ -10,18 +10,19 @@ import {compileComponentFromMetadata, ConstantPool, CssSelector, DEFAULT_INTERPO
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CycleAnalyzer} from '../../cycles';
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics';
|
||||
import {absoluteFrom, relative} from '../../file_system';
|
||||
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {DependencyTracker} from '../../incremental/api';
|
||||
import {IndexingContext} from '../../indexer';
|
||||
import {DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {ClassPropertyMapping, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck/api';
|
||||
import {getTemplateId, makeTemplateDiagnostic} from '../../typecheck/diagnostics';
|
||||
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
|
||||
import {SubsetOfKeys} from '../../util/src/typescript';
|
||||
|
||||
@ -55,6 +56,9 @@ export interface ComponentAnalysisData {
|
||||
template: ParsedTemplateWithSource;
|
||||
metadataStmt: Statement|null;
|
||||
|
||||
inputs: ClassPropertyMapping;
|
||||
outputs: ClassPropertyMapping;
|
||||
|
||||
/**
|
||||
* Providers extracted from the `providers` field of the component annotation which will require
|
||||
* an Angular factory definition at runtime.
|
||||
@ -190,7 +194,7 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
|
||||
// Next, read the `@Component`-specific fields.
|
||||
const {decorator: component, metadata} = directiveResult;
|
||||
const {decorator: component, metadata, inputs, outputs} = directiveResult;
|
||||
|
||||
// Go through the root directories for this project, and select the one with the smallest
|
||||
// relative path representation.
|
||||
@ -254,9 +258,26 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
}
|
||||
|
||||
let diagnostics: ts.Diagnostic[]|undefined = undefined;
|
||||
|
||||
if (template.errors !== undefined) {
|
||||
throw new Error(
|
||||
`Errors parsing template: ${template.errors.map(e => e.toString()).join(', ')}`);
|
||||
// If there are any template parsing errors, convert them to `ts.Diagnostic`s for display.
|
||||
const id = getTemplateId(node);
|
||||
diagnostics = template.errors.map(error => {
|
||||
const span = error.span;
|
||||
|
||||
if (span.start.offset === span.end.offset) {
|
||||
// Template errors can contain zero-length spans, if the error occurs at a single point.
|
||||
// However, TypeScript does not handle displaying a zero-length diagnostic very well, so
|
||||
// increase the ending offset by 1 for such errors, to ensure the position is shown in the
|
||||
// diagnostic.
|
||||
span.end.offset++;
|
||||
}
|
||||
|
||||
return makeTemplateDiagnostic(
|
||||
id, template.sourceMapping, span, ts.DiagnosticCategory.Error,
|
||||
ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR), error.msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Figure out the set of styles. The ordering here is important: external resources (styleUrls)
|
||||
@ -310,6 +331,8 @@ export class ComponentDecoratorHandler implements
|
||||
const output: AnalysisOutput<ComponentAnalysisData> = {
|
||||
analysis: {
|
||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||
inputs,
|
||||
outputs,
|
||||
meta: {
|
||||
...metadata,
|
||||
template: {
|
||||
@ -327,7 +350,7 @@ export class ComponentDecoratorHandler implements
|
||||
i18nUseExternalIds: this.i18nUseExternalIds,
|
||||
relativeContextFilePath,
|
||||
},
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, metadata.inputs, this.reflector),
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||
this.annotateForClosureCompiler),
|
||||
@ -335,6 +358,7 @@ export class ComponentDecoratorHandler implements
|
||||
providersRequiringFactory,
|
||||
viewProvidersRequiringFactory,
|
||||
},
|
||||
diagnostics,
|
||||
};
|
||||
if (changeDetection !== null) {
|
||||
output.analysis!.meta.changeDetection = changeDetection;
|
||||
@ -351,8 +375,8 @@ export class ComponentDecoratorHandler implements
|
||||
name: node.name.text,
|
||||
selector: analysis.meta.selector,
|
||||
exportAs: analysis.meta.exportAs,
|
||||
inputs: analysis.meta.inputs,
|
||||
outputs: analysis.meta.outputs,
|
||||
inputs: analysis.inputs,
|
||||
outputs: analysis.outputs,
|
||||
queries: analysis.meta.queries.map(query => query.propertyName),
|
||||
isComponent: true,
|
||||
baseClass: analysis.baseClass,
|
||||
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||
import {DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {ClassPropertyMapping, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
@ -39,6 +39,8 @@ export interface DirectiveHandlerData {
|
||||
meta: R3DirectiveMetadata;
|
||||
metadataStmt: Statement|null;
|
||||
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||
inputs: ClassPropertyMapping;
|
||||
outputs: ClassPropertyMapping;
|
||||
}
|
||||
|
||||
export class DirectiveDecoratorHandler implements
|
||||
@ -83,11 +85,10 @@ export class DirectiveDecoratorHandler implements
|
||||
const directiveResult = extractDirectiveMetadata(
|
||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
||||
flags, this.annotateForClosureCompiler);
|
||||
const analysis = directiveResult && directiveResult.metadata;
|
||||
|
||||
if (analysis === undefined) {
|
||||
if (directiveResult === undefined) {
|
||||
return {};
|
||||
}
|
||||
const analysis = directiveResult.metadata;
|
||||
|
||||
let providersRequiringFactory: Set<Reference<ClassDeclaration>>|null = null;
|
||||
if (directiveResult !== undefined && directiveResult.decorator.has('providers')) {
|
||||
@ -97,12 +98,14 @@ export class DirectiveDecoratorHandler implements
|
||||
|
||||
return {
|
||||
analysis: {
|
||||
inputs: directiveResult.inputs,
|
||||
outputs: directiveResult.outputs,
|
||||
meta: analysis,
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||
this.annotateForClosureCompiler),
|
||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, analysis.inputs, this.reflector),
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
|
||||
providersRequiringFactory
|
||||
}
|
||||
};
|
||||
@ -117,8 +120,8 @@ export class DirectiveDecoratorHandler implements
|
||||
name: node.name.text,
|
||||
selector: analysis.meta.selector,
|
||||
exportAs: analysis.meta.exportAs,
|
||||
inputs: analysis.meta.inputs,
|
||||
outputs: analysis.meta.outputs,
|
||||
inputs: analysis.inputs,
|
||||
outputs: analysis.outputs,
|
||||
queries: analysis.meta.queries.map(query => query.propertyName),
|
||||
isComponent: false,
|
||||
baseClass: analysis.baseClass,
|
||||
@ -199,8 +202,13 @@ export class DirectiveDecoratorHandler implements
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
|
||||
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
flags: HandlerFlags, annotateForClosureCompiler: boolean, defaultSelector: string|null = null):
|
||||
{decorator: Map<string, ts.Expression>, metadata: R3DirectiveMetadata}|undefined {
|
||||
flags: HandlerFlags, annotateForClosureCompiler: boolean,
|
||||
defaultSelector: string|null = null): {
|
||||
decorator: Map<string, ts.Expression>,
|
||||
metadata: R3DirectiveMetadata,
|
||||
inputs: ClassPropertyMapping,
|
||||
outputs: ClassPropertyMapping,
|
||||
}|undefined {
|
||||
let directive: Map<string, ts.Expression>;
|
||||
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
||||
directive = new Map<string, ts.Expression>();
|
||||
@ -331,6 +339,9 @@ export function extractDirectiveMetadata(
|
||||
const type = wrapTypeReference(reflector, clazz);
|
||||
const internalType = new WrappedNodeExpr(reflector.getInternalNameOfClass(clazz));
|
||||
|
||||
const inputs = ClassPropertyMapping.fromMappedObject({...inputsFromMeta, ...inputsFromFields});
|
||||
const outputs = ClassPropertyMapping.fromMappedObject({...outputsFromMeta, ...outputsFromFields});
|
||||
|
||||
const metadata: R3DirectiveMetadata = {
|
||||
name: clazz.name.text,
|
||||
deps: ctorDeps,
|
||||
@ -338,8 +349,8 @@ export function extractDirectiveMetadata(
|
||||
lifecycle: {
|
||||
usesOnChanges,
|
||||
},
|
||||
inputs: {...inputsFromMeta, ...inputsFromFields},
|
||||
outputs: {...outputsFromMeta, ...outputsFromFields},
|
||||
inputs: inputs.toJointMappedObject(),
|
||||
outputs: outputs.toDirectMappedObject(),
|
||||
queries,
|
||||
viewQueries,
|
||||
selector,
|
||||
@ -352,7 +363,12 @@ export function extractDirectiveMetadata(
|
||||
exportAs,
|
||||
providers
|
||||
};
|
||||
return {decorator: directive, metadata};
|
||||
return {
|
||||
decorator: directive,
|
||||
metadata,
|
||||
inputs,
|
||||
outputs,
|
||||
};
|
||||
}
|
||||
|
||||
export function extractQueryMetadata(
|
||||
|
@ -215,8 +215,10 @@ function createUnsuitableInjectionTokenError(
|
||||
makeRelatedInformation(
|
||||
reason.typeNode,
|
||||
'This type does not have a value, so it cannot be used as injection token.'),
|
||||
makeRelatedInformation(reason.decl, 'The type is declared here.'),
|
||||
];
|
||||
if (reason.decl !== null) {
|
||||
hints.push(makeRelatedInformation(reason.decl, 'The type is declared here.'));
|
||||
}
|
||||
break;
|
||||
case ValueUnavailableKind.TYPE_ONLY_IMPORT:
|
||||
chainMessage =
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CssSelector, DirectiveMeta as T2DirectiveMeta, parseTemplate, R3TargetBinder, SelectorMatcher, TmplAstElement} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
@ -73,6 +74,49 @@ runInEachFileSystem(() => {
|
||||
expect(span.start.toString()).toContain('/entry.ts@5:22');
|
||||
expect(span.end.toString()).toContain('/entry.ts@5:29');
|
||||
});
|
||||
|
||||
it('should produce metadata compatible with template binding', () => {
|
||||
const src = `
|
||||
import {Directive, Input} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class TestDir {
|
||||
@Input('propName')
|
||||
fieldName: string;
|
||||
}
|
||||
`;
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: _('/node_modules/@angular/core/index.d.ts'),
|
||||
contents: 'export const Directive: any; export const Input: any;',
|
||||
},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents: src,
|
||||
},
|
||||
]);
|
||||
|
||||
const analysis = analyzeDirective(program, 'TestDir');
|
||||
const matcher = new SelectorMatcher<T2DirectiveMeta>();
|
||||
const dirMeta: T2DirectiveMeta = {
|
||||
exportAs: null,
|
||||
inputs: analysis.inputs,
|
||||
outputs: analysis.outputs,
|
||||
isComponent: false,
|
||||
name: 'Dir',
|
||||
};
|
||||
matcher.addSelectables(CssSelector.parse('[dir]'), dirMeta);
|
||||
|
||||
const {nodes} = parseTemplate('<div dir [propName]="expr"></div>', 'unimportant.html');
|
||||
const binder = new R3TargetBinder(matcher).bind({template: nodes});
|
||||
const propBinding = (nodes[0] as TmplAstElement).inputs[0];
|
||||
const propBindingConsumer = binder.getConsumerOfBinding(propBinding);
|
||||
|
||||
// Assert that the consumer of the binding is the directive, which means that the metadata
|
||||
// fed into the SelectorMatcher was compatible with the binder, and did not confuse property
|
||||
// and field names.
|
||||
expect(propBindingConsumer).toBe(dirMeta);
|
||||
});
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
@ -34,6 +34,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
@ -28,8 +28,9 @@ import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeRe
|
||||
import {generatedFactoryTransform} from '../../shims';
|
||||
import {ivySwitchTransform} from '../../switch';
|
||||
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
||||
import {isTemplateDiagnostic, TemplateTypeCheckerImpl} from '../../typecheck';
|
||||
import {TemplateTypeCheckerImpl} from '../../typecheck';
|
||||
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api';
|
||||
import {isTemplateDiagnostic} from '../../typecheck/diagnostics';
|
||||
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
|
||||
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
|
||||
|
||||
|
@ -56,6 +56,11 @@ export enum ErrorCode {
|
||||
*/
|
||||
HOST_BINDING_PARSE_ERROR = 5001,
|
||||
|
||||
/**
|
||||
* Raised when the compiler cannot parse a component's template.
|
||||
*/
|
||||
TEMPLATE_PARSE_ERROR = 5002,
|
||||
|
||||
/**
|
||||
* Raised when an NgModule contains an invalid reference in `declarations`.
|
||||
*/
|
||||
|
@ -22,10 +22,10 @@ export class InvalidFileSystem implements FileSystem {
|
||||
readFile(path: AbsoluteFsPath): string {
|
||||
throw makeError();
|
||||
}
|
||||
readFileBuffer(path: AbsoluteFsPath): Buffer {
|
||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array {
|
||||
throw makeError();
|
||||
}
|
||||
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive?: boolean): void {
|
||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive?: boolean): void {
|
||||
throw makeError();
|
||||
}
|
||||
removeFile(path: AbsoluteFsPath): void {
|
||||
|
@ -23,10 +23,10 @@ export class NodeJSFileSystem implements FileSystem {
|
||||
readFile(path: AbsoluteFsPath): string {
|
||||
return fs.readFileSync(path, 'utf8');
|
||||
}
|
||||
readFileBuffer(path: AbsoluteFsPath): Buffer {
|
||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array {
|
||||
return fs.readFileSync(path);
|
||||
}
|
||||
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive: boolean = false): void {
|
||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive: boolean = false): void {
|
||||
fs.writeFileSync(path, data, exclusive ? {flag: 'wx'} : undefined);
|
||||
}
|
||||
removeFile(path: AbsoluteFsPath): void {
|
||||
|
@ -37,8 +37,8 @@ export type PathSegment = BrandedPath<'PathSegment'>;
|
||||
export interface FileSystem {
|
||||
exists(path: AbsoluteFsPath): boolean;
|
||||
readFile(path: AbsoluteFsPath): string;
|
||||
readFileBuffer(path: AbsoluteFsPath): Buffer;
|
||||
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive?: boolean): void;
|
||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array;
|
||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive?: boolean): void;
|
||||
removeFile(path: AbsoluteFsPath): void;
|
||||
symlink(target: AbsoluteFsPath, path: AbsoluteFsPath): void;
|
||||
readdir(path: AbsoluteFsPath): PathSegment[];
|
||||
|
@ -38,16 +38,16 @@ export abstract class MockFileSystem implements FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
readFileBuffer(path: AbsoluteFsPath): Buffer {
|
||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array {
|
||||
const {entity} = this.findFromPath(path);
|
||||
if (isFile(entity)) {
|
||||
return Buffer.isBuffer(entity) ? entity : new Buffer(entity);
|
||||
return entity instanceof Uint8Array ? entity : new Buffer(entity);
|
||||
} else {
|
||||
throw new MockFileSystemError('ENOENT', path, `File "${path}" does not exist.`);
|
||||
}
|
||||
}
|
||||
|
||||
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive: boolean = false): void {
|
||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive: boolean = false): void {
|
||||
const [folderPath, basename] = this.splitIntoFolderAndFile(path);
|
||||
const {entity} = this.findFromPath(folderPath);
|
||||
if (entity === null || !isFolder(entity)) {
|
||||
@ -295,7 +295,7 @@ export type Entity = Folder|File|SymLink;
|
||||
export interface Folder {
|
||||
[pathSegments: string]: Entity;
|
||||
}
|
||||
export type File = string|Buffer;
|
||||
export type File = string|Uint8Array;
|
||||
export class SymLink {
|
||||
constructor(public path: AbsoluteFsPath) {}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, AbsoluteFsPath} from '../../file_system';
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassPropertyMapping} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {ComponentMeta} from '../src/context';
|
||||
@ -50,8 +51,8 @@ export function getBoundTemplate(
|
||||
selector,
|
||||
name: declaration.name.getText(),
|
||||
isComponent: true,
|
||||
inputs: {},
|
||||
outputs: {},
|
||||
inputs: ClassPropertyMapping.fromMappedObject({}),
|
||||
outputs: ClassPropertyMapping.fromMappedObject({}),
|
||||
exportAs: null,
|
||||
});
|
||||
});
|
||||
|
@ -10,3 +10,4 @@ export * from './src/api';
|
||||
export {DtsMetadataReader} from './src/dts';
|
||||
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
||||
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
||||
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
|
||||
|
@ -12,6 +12,8 @@ import * as ts from 'typescript';
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
|
||||
import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
|
||||
|
||||
|
||||
/**
|
||||
* Metadata collected for an `NgModule`.
|
||||
@ -52,25 +54,25 @@ export interface DirectiveTypeCheckMeta {
|
||||
* Directive's class. This allows inputs to accept a wider range of types and coerce the input to
|
||||
* a narrower type with a getter/setter. See https://angular.io/guide/template-typecheck.
|
||||
*/
|
||||
coercedInputFields: Set<string>;
|
||||
coercedInputFields: Set<ClassPropertyName>;
|
||||
|
||||
/**
|
||||
* The set of input fields which map to `readonly`, `private`, or `protected` members in the
|
||||
* Directive's class.
|
||||
*/
|
||||
restrictedInputFields: Set<string>;
|
||||
restrictedInputFields: Set<ClassPropertyName>;
|
||||
|
||||
/**
|
||||
* The set of input fields which are declared as string literal members in the Directive's class.
|
||||
* We need to track these separately because these fields may not be valid JS identifiers so
|
||||
* we cannot use them with property access expressions when assigning inputs.
|
||||
*/
|
||||
stringLiteralInputFields: Set<string>;
|
||||
stringLiteralInputFields: Set<ClassPropertyName>;
|
||||
|
||||
/**
|
||||
* The set of input fields which do not have corresponding members in the Directive's class.
|
||||
*/
|
||||
undeclaredInputFields: Set<string>;
|
||||
undeclaredInputFields: Set<ClassPropertyName>;
|
||||
|
||||
/**
|
||||
* Whether the Directive's class is generic, i.e. `class MyDir<T> {...}`.
|
||||
@ -89,6 +91,16 @@ export interface DirectiveMeta extends T2DirectiveMeta, DirectiveTypeCheckMeta {
|
||||
selector: string|null;
|
||||
queries: string[];
|
||||
|
||||
/**
|
||||
* A mapping of input field names to the property names.
|
||||
*/
|
||||
inputs: ClassPropertyMapping;
|
||||
|
||||
/**
|
||||
* A mapping of output field names to the property names.
|
||||
*/
|
||||
outputs: ClassPropertyMapping;
|
||||
|
||||
/**
|
||||
* A `Reference` to the base class for the directive, if one was detected.
|
||||
*
|
||||
|
@ -12,6 +12,7 @@ import {Reference} from '../../imports';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
|
||||
import {DirectiveMeta, MetadataReader, NgModuleMeta, PipeMeta} from './api';
|
||||
import {ClassPropertyMapping} from './property_mapping';
|
||||
import {extractDirectiveTypeCheckMeta, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
|
||||
|
||||
/**
|
||||
@ -76,7 +77,10 @@ export class DtsMetadataReader implements MetadataReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
const inputs = readStringMapType(def.type.typeArguments[3]);
|
||||
const inputs =
|
||||
ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[3]));
|
||||
const outputs =
|
||||
ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[4]));
|
||||
return {
|
||||
ref,
|
||||
name: clazz.name.text,
|
||||
@ -84,7 +88,7 @@ export class DtsMetadataReader implements MetadataReader {
|
||||
selector: readStringType(def.type.typeArguments[1]),
|
||||
exportAs: readStringArrayType(def.type.typeArguments[2]),
|
||||
inputs,
|
||||
outputs: readStringMapType(def.type.typeArguments[4]),
|
||||
outputs,
|
||||
queries: readStringArrayType(def.type.typeArguments[5]),
|
||||
...extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector),
|
||||
baseClass: readBaseClass(clazz, this.checker, this.reflector),
|
||||
|
@ -7,9 +7,11 @@
|
||||
*/
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {DirectiveMeta, MetadataReader} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
|
||||
import {DirectiveMeta, MetadataReader} from './api';
|
||||
import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
|
||||
|
||||
/**
|
||||
* Given a reference to a directive, return a flattened version of its `DirectiveMeta` metadata
|
||||
* which includes metadata from its entire inheritance chain.
|
||||
@ -25,13 +27,13 @@ export function flattenInheritedDirectiveMetadata(
|
||||
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
|
||||
}
|
||||
|
||||
let inputs: {[key: string]: string|[string, string]} = {};
|
||||
let outputs: {[key: string]: string} = {};
|
||||
const coercedInputFields = new Set<string>();
|
||||
const undeclaredInputFields = new Set<string>();
|
||||
const restrictedInputFields = new Set<string>();
|
||||
const stringLiteralInputFields = new Set<string>();
|
||||
const coercedInputFields = new Set<ClassPropertyName>();
|
||||
const undeclaredInputFields = new Set<ClassPropertyName>();
|
||||
const restrictedInputFields = new Set<ClassPropertyName>();
|
||||
const stringLiteralInputFields = new Set<ClassPropertyName>();
|
||||
let isDynamic = false;
|
||||
let inputs = ClassPropertyMapping.empty();
|
||||
let outputs = ClassPropertyMapping.empty();
|
||||
|
||||
const addMetadata = (meta: DirectiveMeta): void => {
|
||||
if (meta.baseClass === 'dynamic') {
|
||||
@ -45,8 +47,9 @@ export function flattenInheritedDirectiveMetadata(
|
||||
isDynamic = true;
|
||||
}
|
||||
}
|
||||
inputs = {...inputs, ...meta.inputs};
|
||||
outputs = {...outputs, ...meta.outputs};
|
||||
|
||||
inputs = ClassPropertyMapping.merge(inputs, meta.inputs);
|
||||
outputs = ClassPropertyMapping.merge(outputs, meta.outputs);
|
||||
|
||||
for (const coercedInputField of meta.coercedInputFields) {
|
||||
coercedInputFields.add(coercedInputField);
|
||||
|
200
packages/compiler-cli/src/ngtsc/metadata/src/property_mapping.ts
Normal file
200
packages/compiler-cli/src/ngtsc/metadata/src/property_mapping.ts
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {InputOutputPropertySet} from '@angular/compiler';
|
||||
|
||||
/**
|
||||
* The name of a class property that backs an input or output declared by a directive or component.
|
||||
*
|
||||
* This type exists for documentation only.
|
||||
*/
|
||||
export type ClassPropertyName = string;
|
||||
|
||||
/**
|
||||
* The name by which an input or output of a directive or component is bound in an Angular template.
|
||||
*
|
||||
* This type exists for documentation only.
|
||||
*/
|
||||
export type BindingPropertyName = string;
|
||||
|
||||
/**
|
||||
* An input or output of a directive that has both a named JavaScript class property on a component
|
||||
* or directive class, as well as an Angular template property name used for binding.
|
||||
*/
|
||||
export interface InputOrOutput {
|
||||
/**
|
||||
* The name of the JavaScript property on the component or directive instance for this input or
|
||||
* output.
|
||||
*/
|
||||
readonly classPropertyName: ClassPropertyName;
|
||||
|
||||
/**
|
||||
* The property name used to bind this input or output in an Angular template.
|
||||
*/
|
||||
readonly bindingPropertyName: BindingPropertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of component property and template binding property names, for example containing the
|
||||
* inputs of a particular directive or component.
|
||||
*
|
||||
* A single component property has exactly one input/output annotation (and therefore one binding
|
||||
* property name) associated with it, but the same binding property name may be shared across many
|
||||
* component property names.
|
||||
*
|
||||
* Allows bidirectional querying of the mapping - looking up all inputs/outputs with a given
|
||||
* property name, or mapping from a specific class property to its binding property name.
|
||||
*/
|
||||
export class ClassPropertyMapping implements InputOutputPropertySet {
|
||||
/**
|
||||
* Mapping from class property names to the single `InputOrOutput` for that class property.
|
||||
*/
|
||||
private forwardMap: Map<ClassPropertyName, InputOrOutput>;
|
||||
|
||||
/**
|
||||
* Mapping from property names to one or more `InputOrOutput`s which share that name.
|
||||
*/
|
||||
private reverseMap: Map<BindingPropertyName, InputOrOutput[]>;
|
||||
|
||||
private constructor(forwardMap: Map<ClassPropertyName, InputOrOutput>) {
|
||||
this.forwardMap = forwardMap;
|
||||
this.reverseMap = reverseMapFromForwardMap(forwardMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a `ClassPropertyMapping` with no entries.
|
||||
*/
|
||||
static empty(): ClassPropertyMapping {
|
||||
return new ClassPropertyMapping(new Map());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a `ClassPropertyMapping` from a primitive JS object which maps class property names
|
||||
* to either binding property names or an array that contains both names, which is used in on-disk
|
||||
* metadata formats (e.g. in .d.ts files).
|
||||
*/
|
||||
static fromMappedObject(obj: {
|
||||
[classPropertyName: string]: BindingPropertyName|[ClassPropertyName, BindingPropertyName]
|
||||
}): ClassPropertyMapping {
|
||||
const forwardMap = new Map<ClassPropertyName, InputOrOutput>();
|
||||
|
||||
for (const classPropertyName of Object.keys(obj)) {
|
||||
const value = obj[classPropertyName];
|
||||
const bindingPropertyName = Array.isArray(value) ? value[0] : value;
|
||||
const inputOrOutput: InputOrOutput = {classPropertyName, bindingPropertyName};
|
||||
forwardMap.set(classPropertyName, inputOrOutput);
|
||||
}
|
||||
|
||||
return new ClassPropertyMapping(forwardMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two mappings into one, with class properties from `b` taking precedence over class
|
||||
* properties from `a`.
|
||||
*/
|
||||
static merge(a: ClassPropertyMapping, b: ClassPropertyMapping): ClassPropertyMapping {
|
||||
const forwardMap = new Map<ClassPropertyName, InputOrOutput>(a.forwardMap.entries());
|
||||
for (const [classPropertyName, inputOrOutput] of b.forwardMap) {
|
||||
forwardMap.set(classPropertyName, inputOrOutput);
|
||||
}
|
||||
|
||||
return new ClassPropertyMapping(forwardMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* All class property names mapped in this mapping.
|
||||
*/
|
||||
get classPropertyNames(): ClassPropertyName[] {
|
||||
return Array.from(this.forwardMap.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* All binding property names mapped in this mapping.
|
||||
*/
|
||||
get propertyNames(): BindingPropertyName[] {
|
||||
return Array.from(this.reverseMap.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a mapping for the given property name exists.
|
||||
*/
|
||||
hasBindingPropertyName(propertyName: BindingPropertyName): boolean {
|
||||
return this.reverseMap.has(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all `InputOrOutput`s that use this `propertyName`.
|
||||
*/
|
||||
getByBindingPropertyName(propertyName: string): ReadonlyArray<InputOrOutput>|null {
|
||||
return this.reverseMap.has(propertyName) ? this.reverseMap.get(propertyName)! : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the `InputOrOutput` associated with a `classPropertyName`.
|
||||
*/
|
||||
getByClassPropertyName(classPropertyName: string): InputOrOutput|null {
|
||||
return this.forwardMap.has(classPropertyName) ? this.forwardMap.get(classPropertyName)! : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this mapping to a primitive JS object which maps each class property directly to the
|
||||
* binding property name associated with it.
|
||||
*/
|
||||
toDirectMappedObject(): {[classPropertyName: string]: BindingPropertyName} {
|
||||
const obj: {[classPropertyName: string]: BindingPropertyName} = {};
|
||||
for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
|
||||
obj[classPropertyName] = inputOrOutput.bindingPropertyName;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this mapping to a primitive JS object which maps each class property either to itself
|
||||
* (for cases where the binding property name is the same) or to an array which contains both
|
||||
* names if they differ.
|
||||
*
|
||||
* This object format is used when mappings are serialized (for example into .d.ts files).
|
||||
*/
|
||||
toJointMappedObject():
|
||||
{[classPropertyName: string]: BindingPropertyName|[BindingPropertyName, ClassPropertyName]} {
|
||||
const obj: {
|
||||
[classPropertyName: string]: BindingPropertyName|[BindingPropertyName, ClassPropertyName]
|
||||
} = {};
|
||||
for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
|
||||
if (inputOrOutput.bindingPropertyName as string === classPropertyName as string) {
|
||||
obj[classPropertyName] = inputOrOutput.bindingPropertyName;
|
||||
} else {
|
||||
obj[classPropertyName] = [inputOrOutput.bindingPropertyName, classPropertyName];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the iterator protocol and return entry objects which contain the class and binding
|
||||
* property names (and are useful for destructuring).
|
||||
*/
|
||||
* [Symbol.iterator](): IterableIterator<[ClassPropertyName, BindingPropertyName]> {
|
||||
for (const [classPropertyName, inputOrOutput] of this.forwardMap.entries()) {
|
||||
yield [classPropertyName, inputOrOutput.bindingPropertyName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reverseMapFromForwardMap(forwardMap: Map<ClassPropertyName, InputOrOutput>):
|
||||
Map<BindingPropertyName, InputOrOutput[]> {
|
||||
const reverseMap = new Map<BindingPropertyName, InputOrOutput[]>();
|
||||
for (const [_, inputOrOutput] of forwardMap) {
|
||||
if (!reverseMap.has(inputOrOutput.bindingPropertyName)) {
|
||||
reverseMap.set(inputOrOutput.bindingPropertyName, []);
|
||||
}
|
||||
|
||||
reverseMap.get(inputOrOutput.bindingPropertyName)!.push(inputOrOutput);
|
||||
}
|
||||
return reverseMap;
|
||||
}
|
@ -13,6 +13,7 @@ import {ClassDeclaration, ClassMember, ClassMemberKind, isNamedClassDeclaration,
|
||||
import {nodeDebugInfo} from '../../util/src/typescript';
|
||||
|
||||
import {DirectiveMeta, DirectiveTypeCheckMeta, MetadataReader, NgModuleMeta, PipeMeta, TemplateGuardMeta} from './api';
|
||||
import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
|
||||
|
||||
export function extractReferencesFromType(
|
||||
checker: ts.TypeChecker, def: ts.TypeNode, ngModuleImportedFrom: string|null,
|
||||
@ -91,7 +92,7 @@ export function readStringArrayType(type: ts.TypeNode): string[] {
|
||||
* making this metadata invariant to changes of inherited classes.
|
||||
*/
|
||||
export function extractDirectiveTypeCheckMeta(
|
||||
node: ClassDeclaration, inputs: {[fieldName: string]: string|[string, string]},
|
||||
node: ClassDeclaration, inputs: ClassPropertyMapping,
|
||||
reflector: ReflectionHost): DirectiveTypeCheckMeta {
|
||||
const members = reflector.getMembersOfClass(node);
|
||||
const staticMembers = members.filter(member => member.isStatic);
|
||||
@ -102,23 +103,23 @@ export function extractDirectiveTypeCheckMeta(
|
||||
|
||||
const coercedInputFields =
|
||||
new Set(staticMembers.map(extractCoercedInput)
|
||||
.filter((inputName): inputName is string => inputName !== null));
|
||||
.filter((inputName): inputName is ClassPropertyName => inputName !== null));
|
||||
|
||||
const restrictedInputFields = new Set<string>();
|
||||
const stringLiteralInputFields = new Set<string>();
|
||||
const undeclaredInputFields = new Set<string>();
|
||||
const restrictedInputFields = new Set<ClassPropertyName>();
|
||||
const stringLiteralInputFields = new Set<ClassPropertyName>();
|
||||
const undeclaredInputFields = new Set<ClassPropertyName>();
|
||||
|
||||
for (const fieldName of Object.keys(inputs)) {
|
||||
const field = members.find(member => member.name === fieldName);
|
||||
for (const classPropertyName of inputs.classPropertyNames) {
|
||||
const field = members.find(member => member.name === classPropertyName);
|
||||
if (field === undefined || field.node === null) {
|
||||
undeclaredInputFields.add(fieldName);
|
||||
undeclaredInputFields.add(classPropertyName);
|
||||
continue;
|
||||
}
|
||||
if (isRestricted(field.node)) {
|
||||
restrictedInputFields.add(fieldName);
|
||||
restrictedInputFields.add(classPropertyName);
|
||||
}
|
||||
if (field.nameNode !== null && ts.isStringLiteral(field.nameNode)) {
|
||||
stringLiteralInputFields.add(fieldName);
|
||||
stringLiteralInputFields.add(classPropertyName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,7 +336,7 @@ export interface UnsupportedType {
|
||||
export interface NoValueDeclaration {
|
||||
kind: ValueUnavailableKind.NO_VALUE_DECLARATION;
|
||||
typeNode: ts.TypeNode;
|
||||
decl: ts.Declaration;
|
||||
decl: ts.Declaration|null;
|
||||
}
|
||||
|
||||
export interface TypeOnlyImport {
|
||||
|
@ -38,7 +38,11 @@ export function typeToValue(
|
||||
// has a value declaration associated with it. Note that const enums are an exception,
|
||||
// because while they do have a value declaration, they don't exist at runtime.
|
||||
if (decl.valueDeclaration === undefined || decl.flags & ts.SymbolFlags.ConstEnum) {
|
||||
return noValueDeclaration(typeNode, decl.declarations[0]);
|
||||
let typeOnlyDecl: ts.Declaration|null = null;
|
||||
if (decl.declarations !== undefined && decl.declarations.length > 0) {
|
||||
typeOnlyDecl = decl.declarations[0];
|
||||
}
|
||||
return noValueDeclaration(typeNode, typeOnlyDecl);
|
||||
}
|
||||
|
||||
// The type points to a valid value declaration. Rewrite the TypeReference into an
|
||||
@ -140,7 +144,7 @@ function unsupportedType(typeNode: ts.TypeNode): UnavailableTypeValueReference {
|
||||
}
|
||||
|
||||
function noValueDeclaration(
|
||||
typeNode: ts.TypeNode, decl: ts.Declaration): UnavailableTypeValueReference {
|
||||
typeNode: ts.TypeNode, decl: ts.Declaration|null): UnavailableTypeValueReference {
|
||||
return {
|
||||
kind: TypeValueReferenceKind.UNAVAILABLE,
|
||||
reason: {kind: ValueUnavailableKind.NO_VALUE_DECLARATION, typeNode, decl},
|
||||
|
@ -9,7 +9,7 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference, ReferenceEmitter} from '../../imports';
|
||||
import {CompoundMetadataRegistry, DirectiveMeta, LocalMetadataRegistry, MetadataRegistry, PipeMeta} from '../../metadata';
|
||||
import {ClassPropertyMapping, CompoundMetadataRegistry, DirectiveMeta, LocalMetadataRegistry, MetadataRegistry, PipeMeta} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ScopeData} from '../src/api';
|
||||
import {DtsModuleScopeResolver} from '../src/dependency';
|
||||
@ -236,8 +236,8 @@ function fakeDirective(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
||||
name,
|
||||
selector: `[${ref.debugName}]`,
|
||||
isComponent: name.startsWith('Cmp'),
|
||||
inputs: {},
|
||||
outputs: {},
|
||||
inputs: ClassPropertyMapping.fromMappedObject({}),
|
||||
outputs: ClassPropertyMapping.fromMappedObject({}),
|
||||
exportAs: null,
|
||||
queries: [],
|
||||
hasNgTemplateContextGuard: false,
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
@ -212,7 +212,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
|
||||
visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.Identifier {
|
||||
const identifier = ts.createIdentifier(ast.name!);
|
||||
this.setSourceMapRange(identifier, ast);
|
||||
this.setSourceMapRange(identifier, ast.sourceSpan);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@ -244,7 +244,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
const call = ts.createCall(
|
||||
ast.name !== null ? ts.createPropertyAccess(target, ast.name) : target, undefined,
|
||||
ast.args.map(arg => arg.visitExpression(this, context)));
|
||||
this.setSourceMapRange(call, ast);
|
||||
this.setSourceMapRange(call, ast.sourceSpan);
|
||||
return call;
|
||||
}
|
||||
|
||||
@ -255,7 +255,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
if (ast.pure) {
|
||||
ts.addSyntheticLeadingComment(expr, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false);
|
||||
}
|
||||
this.setSourceMapRange(expr, ast);
|
||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
||||
return expr;
|
||||
}
|
||||
|
||||
@ -274,15 +274,15 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
} else {
|
||||
expr = ts.createLiteral(ast.value);
|
||||
}
|
||||
this.setSourceMapRange(expr, ast);
|
||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
||||
return expr;
|
||||
}
|
||||
|
||||
visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression {
|
||||
const localizedString = this.scriptTarget >= ts.ScriptTarget.ES2015 ?
|
||||
createLocalizedStringTaggedTemplate(ast, context, this) :
|
||||
createLocalizedStringFunctionCall(ast, context, this, this.imports);
|
||||
this.setSourceMapRange(localizedString, ast);
|
||||
this.createLocalizedStringTaggedTemplate(ast, context) :
|
||||
this.createLocalizedStringFunctionCall(ast, context);
|
||||
this.setSourceMapRange(localizedString, ast.sourceSpan);
|
||||
return localizedString;
|
||||
}
|
||||
|
||||
@ -395,7 +395,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.ArrayLiteralExpression {
|
||||
const expr =
|
||||
ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context)));
|
||||
this.setSourceMapRange(expr, ast);
|
||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
||||
return expr;
|
||||
}
|
||||
|
||||
@ -405,7 +405,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
entry.quoted ? ts.createLiteral(entry.key) : ts.createIdentifier(entry.key),
|
||||
entry.value.visitExpression(this, context)));
|
||||
const expr = ts.createObjectLiteral(entries);
|
||||
this.setSourceMapRange(expr, ast);
|
||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
||||
return expr;
|
||||
}
|
||||
|
||||
@ -424,9 +424,111 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
return ts.createTypeOf(ast.expr.visitExpression(this, context));
|
||||
}
|
||||
|
||||
private setSourceMapRange(expr: ts.Expression, ast: Expression) {
|
||||
if (ast.sourceSpan) {
|
||||
const {start, end} = ast.sourceSpan;
|
||||
/**
|
||||
* Translate the `LocalizedString` node into a `TaggedTemplateExpression` for ES2015 formatted
|
||||
* output.
|
||||
*/
|
||||
private createLocalizedStringTaggedTemplate(ast: LocalizedString, context: Context):
|
||||
ts.TaggedTemplateExpression {
|
||||
let template: ts.TemplateLiteral;
|
||||
const length = ast.messageParts.length;
|
||||
const metaBlock = ast.serializeI18nHead();
|
||||
if (length === 1) {
|
||||
template = ts.createNoSubstitutionTemplateLiteral(metaBlock.cooked, metaBlock.raw);
|
||||
this.setSourceMapRange(template, ast.getMessagePartSourceSpan(0));
|
||||
} else {
|
||||
// Create the head part
|
||||
const head = ts.createTemplateHead(metaBlock.cooked, metaBlock.raw);
|
||||
this.setSourceMapRange(head, ast.getMessagePartSourceSpan(0));
|
||||
const spans: ts.TemplateSpan[] = [];
|
||||
// Create the middle parts
|
||||
for (let i = 1; i < length - 1; i++) {
|
||||
const resolvedExpression = ast.expressions[i - 1].visitExpression(this, context);
|
||||
this.setSourceMapRange(resolvedExpression, ast.getPlaceholderSourceSpan(i - 1));
|
||||
const templatePart = ast.serializeI18nTemplatePart(i);
|
||||
const templateMiddle = createTemplateMiddle(templatePart.cooked, templatePart.raw);
|
||||
this.setSourceMapRange(templateMiddle, ast.getMessagePartSourceSpan(i));
|
||||
const templateSpan = ts.createTemplateSpan(resolvedExpression, templateMiddle);
|
||||
spans.push(templateSpan);
|
||||
}
|
||||
// Create the tail part
|
||||
const resolvedExpression = ast.expressions[length - 2].visitExpression(this, context);
|
||||
this.setSourceMapRange(resolvedExpression, ast.getPlaceholderSourceSpan(length - 2));
|
||||
const templatePart = ast.serializeI18nTemplatePart(length - 1);
|
||||
const templateTail = createTemplateTail(templatePart.cooked, templatePart.raw);
|
||||
this.setSourceMapRange(templateTail, ast.getMessagePartSourceSpan(length - 1));
|
||||
spans.push(ts.createTemplateSpan(resolvedExpression, templateTail));
|
||||
// Put it all together
|
||||
template = ts.createTemplateExpression(head, spans);
|
||||
}
|
||||
const expression = ts.createTaggedTemplate(ts.createIdentifier('$localize'), template);
|
||||
this.setSourceMapRange(expression, ast.sourceSpan);
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the `LocalizedString` node into a `$localize` call using the imported
|
||||
* `__makeTemplateObject` helper for ES5 formatted output.
|
||||
*/
|
||||
private createLocalizedStringFunctionCall(ast: LocalizedString, context: Context) {
|
||||
// A `$localize` message consists `messageParts` and `expressions`, which get interleaved
|
||||
// together. The interleaved pieces look like:
|
||||
// `[messagePart0, expression0, messagePart1, expression1, messagePart2]`
|
||||
//
|
||||
// Note that there is always a message part at the start and end, and so therefore
|
||||
// `messageParts.length === expressions.length + 1`.
|
||||
//
|
||||
// Each message part may be prefixed with "metadata", which is wrapped in colons (:) delimiters.
|
||||
// The metadata is attached to the first and subsequent message parts by calls to
|
||||
// `serializeI18nHead()` and `serializeI18nTemplatePart()` respectively.
|
||||
|
||||
// The first message part (i.e. `ast.messageParts[0]`) is used to initialize `messageParts`
|
||||
// array.
|
||||
const messageParts = [ast.serializeI18nHead()];
|
||||
const expressions: any[] = [];
|
||||
|
||||
// The rest of the `ast.messageParts` and each of the expressions are `ast.expressions` pushed
|
||||
// into the arrays. Note that `ast.messagePart[i]` corresponds to `expressions[i-1]`
|
||||
for (let i = 1; i < ast.messageParts.length; i++) {
|
||||
expressions.push(ast.expressions[i - 1].visitExpression(this, context));
|
||||
messageParts.push(ast.serializeI18nTemplatePart(i));
|
||||
}
|
||||
|
||||
// The resulting downlevelled tagged template string uses a call to the `__makeTemplateObject()`
|
||||
// helper, so we must ensure it has been imported.
|
||||
const {moduleImport, symbol} =
|
||||
this.imports.generateNamedImport('tslib', '__makeTemplateObject');
|
||||
const __makeTemplateObjectHelper = (moduleImport === null) ?
|
||||
ts.createIdentifier(symbol) :
|
||||
ts.createPropertyAccess(ts.createIdentifier(moduleImport), ts.createIdentifier(symbol));
|
||||
|
||||
// Generate the call in the form:
|
||||
// `$localize(__makeTemplateObject(cookedMessageParts, rawMessageParts), ...expressions);`
|
||||
const cookedLiterals = messageParts.map(
|
||||
(messagePart, i) =>
|
||||
this.createLiteral(messagePart.cooked, ast.getMessagePartSourceSpan(i)));
|
||||
const rawLiterals = messageParts.map(
|
||||
(messagePart, i) => this.createLiteral(messagePart.raw, ast.getMessagePartSourceSpan(i)));
|
||||
return ts.createCall(
|
||||
/* expression */ ts.createIdentifier('$localize'),
|
||||
/* typeArguments */ undefined,
|
||||
/* argumentsArray */[
|
||||
ts.createCall(
|
||||
/* expression */ __makeTemplateObjectHelper,
|
||||
/* typeArguments */ undefined,
|
||||
/* argumentsArray */
|
||||
[
|
||||
ts.createArrayLiteral(cookedLiterals),
|
||||
ts.createArrayLiteral(rawLiterals),
|
||||
]),
|
||||
...expressions,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private setSourceMapRange(expr: ts.Node, sourceSpan: ParseSourceSpan|null) {
|
||||
if (sourceSpan) {
|
||||
const {start, end} = sourceSpan;
|
||||
const {url, content} = start.file;
|
||||
if (url) {
|
||||
if (!this.externalSourceFiles.has(url)) {
|
||||
@ -437,6 +539,12 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createLiteral(text: string, span: ParseSourceSpan|null) {
|
||||
const literal = ts.createStringLiteral(text);
|
||||
this.setSourceMapRange(literal, span);
|
||||
return literal;
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
@ -662,40 +770,6 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the `LocalizedString` node into a `TaggedTemplateExpression` for ES2015 formatted
|
||||
* output.
|
||||
*/
|
||||
function createLocalizedStringTaggedTemplate(
|
||||
ast: LocalizedString, context: Context, visitor: ExpressionVisitor) {
|
||||
let template: ts.TemplateLiteral;
|
||||
const length = ast.messageParts.length;
|
||||
const metaBlock = ast.serializeI18nHead();
|
||||
if (length === 1) {
|
||||
template = ts.createNoSubstitutionTemplateLiteral(metaBlock.cooked, metaBlock.raw);
|
||||
} else {
|
||||
// Create the head part
|
||||
const head = ts.createTemplateHead(metaBlock.cooked, metaBlock.raw);
|
||||
const spans: ts.TemplateSpan[] = [];
|
||||
// Create the middle parts
|
||||
for (let i = 1; i < length - 1; i++) {
|
||||
const resolvedExpression = ast.expressions[i - 1].visitExpression(visitor, context);
|
||||
const templatePart = ast.serializeI18nTemplatePart(i);
|
||||
const templateMiddle = createTemplateMiddle(templatePart.cooked, templatePart.raw);
|
||||
spans.push(ts.createTemplateSpan(resolvedExpression, templateMiddle));
|
||||
}
|
||||
// Create the tail part
|
||||
const resolvedExpression = ast.expressions[length - 2].visitExpression(visitor, context);
|
||||
const templatePart = ast.serializeI18nTemplatePart(length - 1);
|
||||
const templateTail = createTemplateTail(templatePart.cooked, templatePart.raw);
|
||||
spans.push(ts.createTemplateSpan(resolvedExpression, templateTail));
|
||||
// Put it all together
|
||||
template = ts.createTemplateExpression(head, spans);
|
||||
}
|
||||
return ts.createTaggedTemplate(ts.createIdentifier('$localize'), template);
|
||||
}
|
||||
|
||||
|
||||
// HACK: Use this in place of `ts.createTemplateMiddle()`.
|
||||
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
||||
function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
||||
@ -710,59 +784,4 @@ function createTemplateTail(cooked: string, raw: string): ts.TemplateTail {
|
||||
const node: ts.TemplateLiteralLikeNode = ts.createTemplateHead(cooked, raw);
|
||||
(node.kind as ts.SyntaxKind) = ts.SyntaxKind.TemplateTail;
|
||||
return node as ts.TemplateTail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the `LocalizedString` node into a `$localize` call using the imported
|
||||
* `__makeTemplateObject` helper for ES5 formatted output.
|
||||
*/
|
||||
function createLocalizedStringFunctionCall(
|
||||
ast: LocalizedString, context: Context, visitor: ExpressionVisitor, imports: ImportManager) {
|
||||
// A `$localize` message consists `messageParts` and `expressions`, which get interleaved
|
||||
// together. The interleaved pieces look like:
|
||||
// `[messagePart0, expression0, messagePart1, expression1, messagePart2]`
|
||||
//
|
||||
// Note that there is always a message part at the start and end, and so therefore
|
||||
// `messageParts.length === expressions.length + 1`.
|
||||
//
|
||||
// Each message part may be prefixed with "metadata", which is wrapped in colons (:) delimiters.
|
||||
// The metadata is attached to the first and subsequent message parts by calls to
|
||||
// `serializeI18nHead()` and `serializeI18nTemplatePart()` respectively.
|
||||
|
||||
// The first message part (i.e. `ast.messageParts[0]`) is used to initialize `messageParts` array.
|
||||
const messageParts = [ast.serializeI18nHead()];
|
||||
const expressions: any[] = [];
|
||||
|
||||
// The rest of the `ast.messageParts` and each of the expressions are `ast.expressions` pushed
|
||||
// into the arrays. Note that `ast.messagePart[i]` corresponds to `expressions[i-1]`
|
||||
for (let i = 1; i < ast.messageParts.length; i++) {
|
||||
expressions.push(ast.expressions[i - 1].visitExpression(visitor, context));
|
||||
messageParts.push(ast.serializeI18nTemplatePart(i));
|
||||
}
|
||||
|
||||
// The resulting downlevelled tagged template string uses a call to the `__makeTemplateObject()`
|
||||
// helper, so we must ensure it has been imported.
|
||||
const {moduleImport, symbol} = imports.generateNamedImport('tslib', '__makeTemplateObject');
|
||||
const __makeTemplateObjectHelper = (moduleImport === null) ?
|
||||
ts.createIdentifier(symbol) :
|
||||
ts.createPropertyAccess(ts.createIdentifier(moduleImport), ts.createIdentifier(symbol));
|
||||
|
||||
// Generate the call in the form:
|
||||
// `$localize(__makeTemplateObject(cookedMessageParts, rawMessageParts), ...expressions);`
|
||||
return ts.createCall(
|
||||
/* expression */ ts.createIdentifier('$localize'),
|
||||
/* typeArguments */ undefined,
|
||||
/* argumentsArray */[
|
||||
ts.createCall(
|
||||
/* expression */ __makeTemplateObjectHelper,
|
||||
/* typeArguments */ undefined,
|
||||
/* argumentsArray */
|
||||
[
|
||||
ts.createArrayLiteral(
|
||||
messageParts.map(messagePart => ts.createStringLiteral(messagePart.cooked))),
|
||||
ts.createArrayLiteral(
|
||||
messageParts.map(messagePart => ts.createStringLiteral(messagePart.raw))),
|
||||
]),
|
||||
...expressions,
|
||||
]);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/shims:api",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../file_system';
|
||||
import {Reference} from '../../imports';
|
||||
import {DirectiveTypeCheckMeta} from '../../metadata';
|
||||
import {ClassPropertyMapping, DirectiveTypeCheckMeta} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@ import {ClassDeclaration} from '../../reflection';
|
||||
export interface TypeCheckableDirectiveMeta extends DirectiveMeta, DirectiveTypeCheckMeta {
|
||||
ref: Reference<ClassDeclaration>;
|
||||
queries: string[];
|
||||
inputs: ClassPropertyMapping;
|
||||
outputs: ClassPropertyMapping;
|
||||
}
|
||||
|
||||
export type TemplateId = string&{__brand: 'TemplateId'};
|
||||
|
@ -0,0 +1,15 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "diagnostics",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export * from './src/diagnostic';
|
||||
export * from './src/id';
|
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {ParseSourceSpan} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from '../../api';
|
||||
|
||||
/**
|
||||
* A `ts.Diagnostic` with additional information about the diagnostic related to template
|
||||
* type-checking.
|
||||
*/
|
||||
export interface TemplateDiagnostic extends ts.Diagnostic {
|
||||
/**
|
||||
* The component with the template that resulted in this diagnostic.
|
||||
*/
|
||||
componentFile: ts.SourceFile;
|
||||
|
||||
/**
|
||||
* The template id of the component that resulted in this diagnostic.
|
||||
*/
|
||||
templateId: TemplateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
|
||||
*/
|
||||
export function makeTemplateDiagnostic(
|
||||
templateId: TemplateId, mapping: TemplateSourceMapping, span: ParseSourceSpan,
|
||||
category: ts.DiagnosticCategory, code: number, messageText: string|ts.DiagnosticMessageChain,
|
||||
relatedMessage?: {
|
||||
text: string,
|
||||
span: ParseSourceSpan,
|
||||
}): TemplateDiagnostic {
|
||||
if (mapping.type === 'direct') {
|
||||
let relatedInformation: ts.DiagnosticRelatedInformation[]|undefined = undefined;
|
||||
if (relatedMessage !== undefined) {
|
||||
relatedInformation = [{
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
file: mapping.node.getSourceFile(),
|
||||
start: relatedMessage.span.start.offset,
|
||||
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
||||
messageText: relatedMessage.text,
|
||||
}];
|
||||
}
|
||||
// For direct mappings, the error is shown inline as ngtsc was able to pinpoint a string
|
||||
// constant within the `@Component` decorator for the template. This allows us to map the error
|
||||
// directly into the bytes of the source file.
|
||||
return {
|
||||
source: 'ngtsc',
|
||||
code,
|
||||
category,
|
||||
messageText,
|
||||
file: mapping.node.getSourceFile(),
|
||||
componentFile: mapping.node.getSourceFile(),
|
||||
templateId,
|
||||
start: span.start.offset,
|
||||
length: span.end.offset - span.start.offset,
|
||||
relatedInformation,
|
||||
};
|
||||
} else if (mapping.type === 'indirect' || mapping.type === 'external') {
|
||||
// For indirect mappings (template was declared inline, but ngtsc couldn't map it directly
|
||||
// to a string constant in the decorator), the component's file name is given with a suffix
|
||||
// indicating it's not the TS file being displayed, but a template.
|
||||
// For external temoplates, the HTML filename is used.
|
||||
const componentSf = mapping.componentClass.getSourceFile();
|
||||
const componentName = mapping.componentClass.name.text;
|
||||
// TODO(alxhub): remove cast when TS in g3 supports this narrowing.
|
||||
const fileName = mapping.type === 'indirect' ?
|
||||
`${componentSf.fileName} (${componentName} template)` :
|
||||
(mapping as ExternalTemplateSourceMapping).templateUrl;
|
||||
// TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS
|
||||
// parser against the template (HTML is just really syntactically invalid TypeScript code ;).
|
||||
// Also investigate caching the file to avoid running the parser multiple times.
|
||||
const sf = ts.createSourceFile(
|
||||
fileName, mapping.template, ts.ScriptTarget.Latest, false, ts.ScriptKind.JSX);
|
||||
|
||||
let relatedInformation: ts.DiagnosticRelatedInformation[] = [];
|
||||
if (relatedMessage !== undefined) {
|
||||
relatedInformation.push({
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
file: sf,
|
||||
start: relatedMessage.span.start.offset,
|
||||
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
||||
messageText: relatedMessage.text,
|
||||
});
|
||||
}
|
||||
|
||||
relatedInformation.push({
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
file: componentSf,
|
||||
// mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
|
||||
// and getEnd() are used because they don't include surrounding whitespace.
|
||||
start: mapping.node.getStart(),
|
||||
length: mapping.node.getEnd() - mapping.node.getStart(),
|
||||
messageText: `Error occurs in the template of component ${componentName}.`,
|
||||
});
|
||||
|
||||
return {
|
||||
source: 'ngtsc',
|
||||
category,
|
||||
code,
|
||||
messageText,
|
||||
file: sf,
|
||||
componentFile: componentSf,
|
||||
templateId,
|
||||
start: span.start.offset,
|
||||
length: span.end.offset - span.start.offset,
|
||||
// Show a secondary message indicating the component whose template contains the error.
|
||||
relatedInformation,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected source mapping type: ${(mapping as {type: string}).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function isTemplateDiagnostic(diagnostic: ts.Diagnostic): diagnostic is TemplateDiagnostic {
|
||||
return diagnostic.hasOwnProperty('componentFile') &&
|
||||
ts.isSourceFile((diagnostic as any).componentFile);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {TemplateId} from '../../api';
|
||||
|
||||
|
||||
const TEMPLATE_ID = Symbol('ngTemplateId');
|
||||
const NEXT_TEMPLATE_ID = Symbol('ngNextTemplateId');
|
||||
|
||||
interface HasTemplateId {
|
||||
[TEMPLATE_ID]: TemplateId;
|
||||
}
|
||||
|
||||
interface HasNextTemplateId {
|
||||
[NEXT_TEMPLATE_ID]: number;
|
||||
}
|
||||
|
||||
export function getTemplateId(clazz: ts.Declaration): TemplateId {
|
||||
const node = clazz as ts.Declaration & Partial<HasTemplateId>;
|
||||
if (node[TEMPLATE_ID] === undefined) {
|
||||
node[TEMPLATE_ID] = allocateTemplateId(node.getSourceFile());
|
||||
}
|
||||
return node[TEMPLATE_ID]!;
|
||||
}
|
||||
|
||||
function allocateTemplateId(sf: ts.SourceFile&Partial<HasNextTemplateId>): TemplateId {
|
||||
if (sf[NEXT_TEMPLATE_ID] === undefined) {
|
||||
sf[NEXT_TEMPLATE_ID] = 1;
|
||||
}
|
||||
return (`tcb${sf[NEXT_TEMPLATE_ID]!++}`) as TemplateId;
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
export {ReusedProgramStrategy} from './src/augmented_program';
|
||||
export {FileTypeCheckingData, TemplateTypeCheckerImpl} from './src/checker';
|
||||
export {TypeCheckContextImpl} from './src/context';
|
||||
export {isTemplateDiagnostic, TemplateDiagnostic} from './src/diagnostics';
|
||||
export {TypeCheckProgramHost} from './src/host';
|
||||
export {TypeCheckShimGenerator} from './src/shim';
|
||||
export {typeCheckFilePath} from './src/type_check_file';
|
||||
|
@ -16,9 +16,10 @@ import {ReflectionHost} from '../../reflection';
|
||||
import {isShim} from '../../shims';
|
||||
import {getSourceFileOrNull} from '../../util/src/typescript';
|
||||
import {OptimizeFor, ProgramTypeCheckAdapter, TemplateId, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
|
||||
import {TemplateDiagnostic} from '../diagnostics';
|
||||
|
||||
import {InliningMode, ShimTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from './context';
|
||||
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
|
||||
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
|
||||
import {TemplateSourceManager} from './source';
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,8 @@ import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
import {ImportManager} from '../../translator';
|
||||
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
||||
import {TemplateDiagnostic} from '../diagnostics';
|
||||
|
||||
import {TemplateDiagnostic} from './diagnostics';
|
||||
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
|
||||
import {Environment} from './environment';
|
||||
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
|
||||
@ -241,8 +241,8 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||
// it comes from a .d.ts file. .d.ts declarations don't have bodies.
|
||||
body: !dirNode.getSourceFile().isDeclarationFile,
|
||||
fields: {
|
||||
inputs: Object.keys(dir.inputs),
|
||||
outputs: Object.keys(dir.outputs),
|
||||
inputs: dir.inputs.classPropertyNames,
|
||||
outputs: dir.outputs.classPropertyNames,
|
||||
// TODO(alxhub): support queries
|
||||
queries: dir.queries,
|
||||
},
|
||||
|
@ -10,23 +10,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {getTokenAtPosition} from '../../util/src/typescript';
|
||||
import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from '../api';
|
||||
|
||||
|
||||
/**
|
||||
* A `ts.Diagnostic` with additional information about the diagnostic related to template
|
||||
* type-checking.
|
||||
*/
|
||||
export interface TemplateDiagnostic extends ts.Diagnostic {
|
||||
/**
|
||||
* The component with the template that resulted in this diagnostic.
|
||||
*/
|
||||
componentFile: ts.SourceFile;
|
||||
|
||||
/**
|
||||
* The template id of the component that resulted in this diagnostic.
|
||||
*/
|
||||
templateId: TemplateId;
|
||||
}
|
||||
import {makeTemplateDiagnostic, TemplateDiagnostic} from '../diagnostics';
|
||||
|
||||
/**
|
||||
* Adapter interface which allows the template type-checking diagnostics code to interpret offsets
|
||||
@ -157,101 +141,6 @@ export function findTypeCheckBlock(file: ts.SourceFile, id: TemplateId): ts.Node
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
|
||||
*/
|
||||
export function makeTemplateDiagnostic(
|
||||
templateId: TemplateId, mapping: TemplateSourceMapping, span: ParseSourceSpan,
|
||||
category: ts.DiagnosticCategory, code: number, messageText: string|ts.DiagnosticMessageChain,
|
||||
relatedMessage?: {
|
||||
text: string,
|
||||
span: ParseSourceSpan,
|
||||
}): TemplateDiagnostic {
|
||||
if (mapping.type === 'direct') {
|
||||
let relatedInformation: ts.DiagnosticRelatedInformation[]|undefined = undefined;
|
||||
if (relatedMessage !== undefined) {
|
||||
relatedInformation = [{
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
file: mapping.node.getSourceFile(),
|
||||
start: relatedMessage.span.start.offset,
|
||||
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
||||
messageText: relatedMessage.text,
|
||||
}];
|
||||
}
|
||||
// For direct mappings, the error is shown inline as ngtsc was able to pinpoint a string
|
||||
// constant within the `@Component` decorator for the template. This allows us to map the error
|
||||
// directly into the bytes of the source file.
|
||||
return {
|
||||
source: 'ngtsc',
|
||||
code,
|
||||
category,
|
||||
messageText,
|
||||
file: mapping.node.getSourceFile(),
|
||||
componentFile: mapping.node.getSourceFile(),
|
||||
templateId,
|
||||
start: span.start.offset,
|
||||
length: span.end.offset - span.start.offset,
|
||||
relatedInformation,
|
||||
};
|
||||
} else if (mapping.type === 'indirect' || mapping.type === 'external') {
|
||||
// For indirect mappings (template was declared inline, but ngtsc couldn't map it directly
|
||||
// to a string constant in the decorator), the component's file name is given with a suffix
|
||||
// indicating it's not the TS file being displayed, but a template.
|
||||
// For external temoplates, the HTML filename is used.
|
||||
const componentSf = mapping.componentClass.getSourceFile();
|
||||
const componentName = mapping.componentClass.name.text;
|
||||
// TODO(alxhub): remove cast when TS in g3 supports this narrowing.
|
||||
const fileName = mapping.type === 'indirect' ?
|
||||
`${componentSf.fileName} (${componentName} template)` :
|
||||
(mapping as ExternalTemplateSourceMapping).templateUrl;
|
||||
// TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS
|
||||
// parser against the template (HTML is just really syntactically invalid TypeScript code ;).
|
||||
// Also investigate caching the file to avoid running the parser multiple times.
|
||||
const sf = ts.createSourceFile(
|
||||
fileName, mapping.template, ts.ScriptTarget.Latest, false, ts.ScriptKind.JSX);
|
||||
|
||||
let relatedInformation: ts.DiagnosticRelatedInformation[] = [];
|
||||
if (relatedMessage !== undefined) {
|
||||
relatedInformation.push({
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
file: sf,
|
||||
start: relatedMessage.span.start.offset,
|
||||
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
||||
messageText: relatedMessage.text,
|
||||
});
|
||||
}
|
||||
|
||||
relatedInformation.push({
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
file: componentSf,
|
||||
// mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
|
||||
// and getEnd() are used because they don't include surrounding whitespace.
|
||||
start: mapping.node.getStart(),
|
||||
length: mapping.node.getEnd() - mapping.node.getStart(),
|
||||
messageText: `Error occurs in the template of component ${componentName}.`,
|
||||
});
|
||||
|
||||
return {
|
||||
source: 'ngtsc',
|
||||
category,
|
||||
code,
|
||||
messageText,
|
||||
file: sf,
|
||||
componentFile: componentSf,
|
||||
templateId,
|
||||
start: span.start.offset,
|
||||
length: span.end.offset - span.start.offset,
|
||||
// Show a secondary message indicating the component whose template contains the error.
|
||||
relatedInformation,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected source mapping type: ${(mapping as {type: string}).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
interface SourceLocation {
|
||||
id: TemplateId;
|
||||
span: AbsoluteSourceSpan;
|
||||
@ -338,8 +227,3 @@ function hasIgnoreMarker(node: ts.Node, sourceFile: ts.SourceFile): boolean {
|
||||
return commentText === IGNORE_MARKER;
|
||||
}) === true;
|
||||
}
|
||||
|
||||
export function isTemplateDiagnostic(diagnostic: ts.Diagnostic): diagnostic is TemplateDiagnostic {
|
||||
return diagnostic.hasOwnProperty('componentFile') &&
|
||||
ts.isSourceFile((diagnostic as any).componentFile);
|
||||
}
|
||||
|
@ -11,8 +11,9 @@ import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, ngErrorCode} from '../../diagnostics';
|
||||
import {TemplateId} from '../api';
|
||||
import {makeTemplateDiagnostic, TemplateDiagnostic} from '../diagnostics';
|
||||
|
||||
import {makeTemplateDiagnostic, TemplateDiagnostic, TemplateSourceResolver} from './diagnostics';
|
||||
import {TemplateSourceResolver} from './diagnostics';
|
||||
|
||||
const REGISTRY = new DomElementSchemaRegistry();
|
||||
const REMOVE_XHTML_REGEX = /^:xhtml:/;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user