Compare commits
68 Commits
zone.js-0.
...
10.1.0
Author | SHA1 | Date | |
---|---|---|---|
c01bd0fe8e | |||
5588324802 | |||
437ecc8090 | |||
0dda97ea66 | |||
5e4aeaa348 | |||
cbbf8b542f | |||
91dfb18840 | |||
e44ddf5baa | |||
6b1a505566 | |||
659705ad78 | |||
8864b0ed69 | |||
4e596b672f | |||
83866827c3 | |||
7006cac50a | |||
dd82f2fefd | |||
bf003340ab | |||
5e35edd724 | |||
c132dcd0ae | |||
bbe331569b | |||
21e9a0032c | |||
fb06903237 | |||
ecc6fd0d28 | |||
80cab26023 | |||
841dfa68f9 | |||
f0af387f6c | |||
14e90bef58 | |||
db3a21b382 | |||
b8351f3b10 | |||
81053d3160 | |||
bdba1a062d | |||
23f855b300 | |||
94a3e0e81d | |||
9cbde86534 | |||
18e474f522 | |||
cb3db0d31b | |||
d36828a7a1 | |||
f18e2d5898 | |||
375f0a6f67 | |||
281b647f15 | |||
0fc44e0436 | |||
201a546af8 | |||
6e643d9874 | |||
4985267211 | |||
b48cc6ead5 | |||
874792dc43 | |||
e7da4040d6 | |||
2a643e1ab6 | |||
364284b0dc | |||
956b25a100 | |||
8017ca4db3 | |||
22f1ac3e37 | |||
4ee5e730ab | |||
6442875c99 | |||
8f24bc9443 | |||
ac461e1efd | |||
f245c6bb15 | |||
68a9a01a64 | |||
8cd4099db9 | |||
0b54c0c6b4 | |||
1ec609946f | |||
7ad32649c0 | |||
9ad69c1503 | |||
9af2de821c | |||
0270020ac2 | |||
6b662d10c1 | |||
55fd725e74 | |||
f77fd5e02a | |||
63ba74fe4e |
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',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
232
CHANGELOG.md
232
CHANGELOG.md
@ -1,26 +1,108 @@
|
||||
<a name="10.1.0-next.6"></a>
|
||||
# 10.1.0-next.6 (2020-08-17)
|
||||
<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)
|
||||
|
||||
|
||||
### 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))
|
||||
* **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))
|
||||
* **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)
|
||||
|
||||
### 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))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.14"></a>
|
||||
## 10.0.14 (2020-08-26)
|
||||
|
||||
|
||||
<a name="10.0.12"></a>
|
||||
## 10.0.12 (2020-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** adding references to const enums in runtime code ([#38542](https://github.com/angular/angular/issues/38542)) ([814b436](https://github.com/angular/angular/commit/814b436)), closes [#38513](https://github.com/angular/angular/issues/38513)
|
||||
* **core:** remove closing body tag from inert DOM builder ([#38454](https://github.com/angular/angular/issues/38454)) ([5528536](https://github.com/angular/angular/commit/5528536))
|
||||
* **localize:** include the last placeholder in parsed translation text ([#38452](https://github.com/angular/angular/issues/38452)) ([57d1a48](https://github.com/angular/angular/commit/57d1a48))
|
||||
* **localize:** parse all parts of a translation with nested HTML ([#38452](https://github.com/angular/angular/issues/38452)) ([07b99f5](https://github.com/angular/angular/commit/07b99f5)), closes [#38422](https://github.com/angular/angular/issues/38422)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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.0.11"></a>
|
||||
## 10.0.11 (2020-08-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **router:** ensure routerLinkActive updates when associated routerLinks change (resubmit of [#38349](https://github.com/angular/angular/issues/38349)) ([#38511](https://github.com/angular/angular/issues/38511)) ([0af9533](https://github.com/angular/angular/commit/0af9533)), closes [#18469](https://github.com/angular/angular/issues/18469)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.10"></a>
|
||||
## 10.0.10 (2020-08-17)
|
||||
|
||||
@ -36,25 +118,6 @@
|
||||
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
@ -76,24 +139,6 @@
|
||||
* **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)
|
||||
|
||||
@ -115,16 +160,6 @@
|
||||
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
@ -138,23 +173,6 @@
|
||||
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
@ -189,62 +207,6 @@
|
||||
* **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)
|
||||
|
||||
@ -526,12 +488,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.
|
||||
@ -539,17 +501,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.
|
||||
|
||||
@ -1877,7 +1839,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
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -17,7 +17,7 @@ An NgModule is defined by a class decorated with `@NgModule()`. The `@NgModule()
|
||||
|
||||
* `imports`: Other modules whose exported classes are needed by component templates declared in *this* NgModule.
|
||||
|
||||
* `providers`: Creators of [services](guide/architecture-services) that this NgModule contributes to the global collection of services; they become accessible in all parts of the app. (You can also specify providers at the component level, which is often preferred.)
|
||||
* `providers`: Creators of [services](guide/architecture-services) that this NgModule contributes to the global collection of services; they become accessible in all parts of the app. (You can also specify providers at the component level.)
|
||||
|
||||
* `bootstrap`: The main application view, called the *root component*, which hosts all other app views. Only the *root NgModule* should set the `bootstrap` property.
|
||||
|
||||
|
@ -154,7 +154,7 @@ Attributes can be changed by `setAttribute()`, which re-initializes correspondin
|
||||
</div>
|
||||
|
||||
For more information, see the [MDN Interfaces documentation](https://developer.mozilla.org/en-US/docs/Web/API#Interfaces) which has API docs for all the standard DOM elements and their properties.
|
||||
Comparing the [`<td>` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td) attributes to the [`<td>` properties](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) provides a helpful example for differentiation.
|
||||
Comparing the [`<td>` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td) to the [`<td>` properties](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) provides a helpful example for differentiation.
|
||||
In particular, you can navigate from the attributes page to the properties via "DOM interface" link, and navigate the inheritance hierarchy up to `HTMLTableCellElement`.
|
||||
|
||||
|
||||
@ -195,7 +195,7 @@ To control the state of the button, set the `disabled` *property*,
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Though you could technically set the `[attr.disabled]` attribute binding, the values are different in that the property binding requires to a boolean value, while its corresponding attribute binding relies on whether the value is `null` or not. Consider the following:
|
||||
Though you could technically set the `[attr.disabled]` attribute binding, the values are different in that the property binding requires to be a boolean value, while its corresponding attribute binding relies on whether the value is `null` or not. Consider the following:
|
||||
|
||||
```html
|
||||
<input [disabled]="condition ? true : false">
|
||||
|
@ -12,7 +12,7 @@ Every application has at least one Angular module, the _root_ module,
|
||||
which must be present for bootstrapping the application on launch.
|
||||
By convention and by default, this NgModule is named `AppModule`.
|
||||
|
||||
When you use the [Angular CLI](cli) command `ng new` to generate an app, the default `AppModule` is as follows.
|
||||
When you use the [Angular CLI](cli) command `ng new` to generate an app, the default `AppModule` looks like the following:
|
||||
|
||||
```typescript
|
||||
/* JavaScript imports */
|
||||
@ -90,8 +90,6 @@ A declarable can only belong to one module, so only declare it in
|
||||
one `@NgModule`. When you need it elsewhere,
|
||||
import the module that has the declarable you need in it.
|
||||
|
||||
**Only `@NgModule` references** go in the `imports` array.
|
||||
|
||||
|
||||
### Using directives with `@NgModule`
|
||||
|
||||
@ -133,7 +131,7 @@ The module's `imports` array appears exclusively in the `@NgModule` metadata obj
|
||||
It tells Angular about other NgModules that this particular module needs to function properly.
|
||||
|
||||
This list of modules are those that export components, directives, or pipes
|
||||
that the component templates in this module reference. In this case, the component is
|
||||
that component templates in this module reference. In this case, the component is
|
||||
`AppComponent`, which references components, directives, or pipes in `BrowserModule`,
|
||||
`FormsModule`, or `HttpClientModule`.
|
||||
A component template can reference another component, directive,
|
||||
|
@ -79,7 +79,7 @@ To incorporate the feature module into your app, you have to let the root module
|
||||
<code-example path="feature-modules/src/app/app.module.ts" region="app-module" header="src/app/app.module.ts"></code-example>
|
||||
|
||||
|
||||
Now the `AppModule` knows about the feature module. If you were to add any service providers to the feature module, `AppModule` would know about those too, as would any other feature modules. However, NgModules don’t expose their components.
|
||||
Now the `AppModule` knows about the feature module. If you were to add any service providers to the feature module, `AppModule` would know about those too, as would any other feature modules. However, NgModules don’t expose their components by default.
|
||||
|
||||
|
||||
## Rendering a feature module’s component template
|
||||
|
@ -766,8 +766,10 @@ The HTML `base` tag with the `href` attribute specifies the base URI, or URL, fo
|
||||
"i18n": {
|
||||
"sourceLocale": "en-US",
|
||||
"locales": {
|
||||
"fr": "src/locale/messages.fr.xlf"
|
||||
"baseHref": ""
|
||||
"fr": {
|
||||
"translation": "src/locale/messages.fr.xlf",
|
||||
"baseHref": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"architect": {
|
||||
|
@ -208,7 +208,7 @@ about the event and gives that data to the parent.
|
||||
The child's template has two controls. The first is an HTML `<input>` with a
|
||||
[template reference variable](guide/template-reference-variables) , `#newItem`,
|
||||
where the user types in an item name. Whatever the user types
|
||||
into the `<input>` gets stored in the `#newItem` variable.
|
||||
into the `<input>` gets stored in the `value` property of the `#newItem` variable.
|
||||
|
||||
<code-example path="inputs-outputs/src/app/item-output/item-output.component.html" region="child-output" header="src/app/item-output/item-output.component.html"></code-example>
|
||||
|
||||
@ -218,7 +218,7 @@ an event binding because the part to the left of the equal
|
||||
sign is in parentheses, `(click)`.
|
||||
|
||||
The `(click)` event is bound to the `addNewItem()` method in the child component class which
|
||||
takes as its argument whatever the value of `#newItem` is.
|
||||
takes as its argument whatever the value of `#newItem.value` property is.
|
||||
|
||||
Now the child component has an `@Output()`
|
||||
for sending data to the parent and a method for raising an event.
|
||||
|
@ -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.
|
||||
|
@ -101,7 +101,7 @@ should import `BrowserModule` from `@angular/platform-browser`.
|
||||
`BrowserModule` provides services that are essential to launch and run a browser app.
|
||||
|
||||
`BrowserModule` also re-exports `CommonModule` from `@angular/common`,
|
||||
which means that components in the `AppModule` module also have access to
|
||||
which means that components in the `AppModule` also have access to
|
||||
the Angular directives every app needs, such as `NgIf` and `NgFor`.
|
||||
|
||||
Do not import `BrowserModule` in any other module.
|
||||
@ -140,7 +140,7 @@ declared in this NgModule.
|
||||
You _can_ export any declarable class—components, directives, and pipes—whether
|
||||
it's declared in this NgModule or in an imported NgModule.
|
||||
|
||||
You _can_ re-export entire imported NgModules, which effectively re-exports all of their exported classes.
|
||||
You _can_ re-export entire imported NgModules, which effectively re-export all of their exported classes.
|
||||
An NgModule can even export a module that it doesn't import.
|
||||
|
||||
<hr/>
|
||||
@ -192,7 +192,7 @@ Its only purpose is to add http service providers to the application as a whole.
|
||||
|
||||
The `forRoot()` static method is a convention that makes it easy for developers to configure services and providers that are intended to be singletons. A good example of `forRoot()` is the `RouterModule.forRoot()` method.
|
||||
|
||||
Apps pass a `Routes` object to `RouterModule.forRoot()` in order to configure the app-wide `Router` service with routes.
|
||||
Apps pass a `Routes` array to `RouterModule.forRoot()` in order to configure the app-wide `Router` service with routes.
|
||||
`RouterModule.forRoot()` returns a [ModuleWithProviders](api/core/ModuleWithProviders).
|
||||
You add that result to the `imports` list of the root `AppModule`.
|
||||
|
||||
|
@ -36,7 +36,7 @@ NgModule metadata does the following:
|
||||
* Declares which components, directives, and pipes belong to the module.
|
||||
* Makes some of those components, directives, and pipes public so that other module's component templates can use them.
|
||||
* Imports other modules with the components, directives, and pipes that components in the current module need.
|
||||
* Provides services that the other application components can use.
|
||||
* Provides services that other application components can use.
|
||||
|
||||
Every Angular app has at least one module, the root module.
|
||||
You [bootstrap](guide/bootstrapping) that module to launch the application.
|
||||
|
@ -37,9 +37,9 @@ by HTML.
|
||||
|
||||
<code-example path="template-reference-variables/src/app/app.component.html" region="ngForm" header="src/app/hero-form.component.html"></code-example>
|
||||
|
||||
The reference value of itemForm, without the ngForm attribute value, would be
|
||||
The reference value of `itemForm`, without the `ngForm` attribute value, would be
|
||||
the [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement).
|
||||
There is, however, a difference between a Component and a Directive in that a `Component`
|
||||
There is, however, a difference between a `Component` and a `Directive` in that a `Component`
|
||||
will be referenced without specifying the attribute value, and a `Directive` will not
|
||||
change the implicit reference (that is, the element).
|
||||
|
||||
|
@ -375,6 +375,5 @@ Some noteworthy observations:
|
||||
When you're filtering by CSS selector and only testing properties of a browser's _native element_, the `By.css` approach may be overkill.
|
||||
|
||||
It's often easier and more clear to filter with a standard `HTMLElement` method
|
||||
such as `querySelector()` or `querySelectorAll()`,
|
||||
as you'll see in the next set of tests.
|
||||
such as `querySelector()` or `querySelectorAll()`.
|
||||
|
||||
|
@ -190,7 +190,7 @@ It knows who the user is based on a property of the injected `UserService`:
|
||||
<code-example path="testing/src/app/welcome/welcome.component.ts" header="app/welcome/welcome.component.ts"></code-example>
|
||||
|
||||
The `WelcomeComponent` has decision logic that interacts with the service, logic that makes this component worth testing.
|
||||
Here's the testing module configuration for the spec file, `app/welcome/welcome.component.spec.ts`:
|
||||
Here's the testing module configuration for the spec file:
|
||||
|
||||
<code-example path="testing/src/app/welcome/welcome.component.spec.ts" region="config-test-module" header="app/welcome/welcome.component.spec.ts"></code-example>
|
||||
|
||||
@ -415,7 +415,7 @@ You do have to call [tick()](api/core/testing/tick) to advance the (virtual) clo
|
||||
Calling [tick()](api/core/testing/tick) simulates the passage of time until all pending asynchronous activities finish.
|
||||
In this case, it waits for the error handler's `setTimeout()`.
|
||||
|
||||
The [tick()](api/core/testing/tick) function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called `processNewMacroTasksSynchronously` (defaults to true) represents whether to invoke new generated macro tasks when ticking.
|
||||
The [tick()](api/core/testing/tick) function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called `processNewMacroTasksSynchronously` (defaults to true) that represents whether to invoke new generated macro tasks when ticking.
|
||||
|
||||
<code-example
|
||||
path="testing/src/app/demo/async-helper.spec.ts"
|
||||
@ -594,11 +594,6 @@ Then you can assert that the quote element displays the expected text.
|
||||
To use `waitForAsync()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file.
|
||||
If you created your project with the Angular CLI, `zone-testing` is already imported in `src/test.ts`.
|
||||
|
||||
The `fakeAsync()` utility function has a few limitations.
|
||||
In particular, it won't work if the test body makes an `XMLHttpRequest` (XHR) call.
|
||||
XHR calls within a test are rare so you can generally stick with [`fakeAsync()`](#fake-async).
|
||||
But if you ever do need to call `XMLHttpRequest`, you'll want to know about `waitForAsync()`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `TestBed.compileComponents()` method (see [below](#compile-components)) calls `XHR`
|
||||
@ -1231,7 +1226,7 @@ and provide for _all_ services injected in _any_ component in the tree.
|
||||
That's too much effort just to answer a few simple questions about links.
|
||||
|
||||
This section describes two techniques for minimizing the setup.
|
||||
Use them, alone or in combination, to stay focused on the testing the primary component.
|
||||
Use them, alone or in combination, to stay focused on testing the primary component.
|
||||
|
||||
{@a stub-component}
|
||||
|
||||
@ -1340,7 +1335,7 @@ The `HostListener` wires the click event of the host element
|
||||
Clicking the anchor should trigger the `onClick()` method,
|
||||
which sets the stub's telltale `navigatedTo` property.
|
||||
Tests inspect `navigatedTo` to confirm that clicking the anchor
|
||||
set the expected route definition.
|
||||
sets the expected route definition.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -1573,7 +1568,7 @@ calls to other `TestBed` static methods such as `compileComponents()`.
|
||||
In this example, the `BannerComponent` is the only component to compile.
|
||||
Other examples configure the testing module with multiple components
|
||||
and may import application modules that hold yet more components.
|
||||
Any of them could be require external files.
|
||||
Any of them could require external files.
|
||||
|
||||
The `TestBed.compileComponents` method asynchronously compiles all components configured in the testing module.
|
||||
|
||||
|
@ -19,7 +19,7 @@ Here's a summary of the stand-alone functions, in order of likely utility:
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align: top">
|
||||
<code>async</code>
|
||||
<code>waitForAsync</code>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
@ -75,7 +75,7 @@ The tests run again, the browser refreshes, and the new test results appear.
|
||||
|
||||
The CLI takes care of Jasmine and Karma configuration for you.
|
||||
|
||||
You can fine-tune many options by editing the `karma.conf.js` and
|
||||
You can fine-tune many options by editing the `karma.conf.js` in the root folder of the project and
|
||||
the `test.ts` files in the `src/` folder.
|
||||
|
||||
The `karma.conf.js` file is a partial Karma configuration file.
|
||||
@ -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.
|
||||
|
||||
|
@ -713,9 +713,9 @@
|
||||
"santosh": {
|
||||
"name": "Santosh Yadav",
|
||||
"picture": "santoshyadav.jpg",
|
||||
"twitter": "Santosh19742211",
|
||||
"twitter": "SantoshYadavDev",
|
||||
"website": "https://www.santoshyadav.dev",
|
||||
"bio": "Santosh is a GDE for Angular and Web Technologies and loves to contribute to Open Source. He is the creator of ng deploy for netlify and core team member for NestJS Addons. He writes for AngularInDepth, mentors for DotNetTricks, organizes Pune Tech Meetup, and conducts free workshops on Angular.",
|
||||
"bio": "Santosh is a GDE for Angular and loves to contribute to Open Source. He is the creator of ng deploy for netlify and core team member for NestJS Addons. He writes for AngularInDepth, mentors for DotNetTricks, organizes Pune Tech Meetup, and conducts free workshops on Angular.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"josephperrott": {
|
||||
|
@ -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.
|
||||
|
@ -23,7 +23,7 @@
|
||||
"build-local-with-viewengine": "yarn ~~build",
|
||||
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
|
||||
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js b0b27361d",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js ef770f1cb",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
|
@ -214,7 +214,7 @@ code {
|
||||
margin-left: 2px;
|
||||
position: relative;
|
||||
@include line-height(24);
|
||||
vertical-align: bottom;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -4,6 +4,7 @@ load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
ts_library(
|
||||
name = "commit-message",
|
||||
srcs = [
|
||||
"builder.ts",
|
||||
"cli.ts",
|
||||
"commit-message-draft.ts",
|
||||
"config.ts",
|
||||
@ -12,14 +13,17 @@ ts_library(
|
||||
"validate.ts",
|
||||
"validate-file.ts",
|
||||
"validate-range.ts",
|
||||
"wizard.ts",
|
||||
],
|
||||
module_name = "@angular/dev-infra-private/commit-message",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/inquirer",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//@types/yargs",
|
||||
"@npm//inquirer",
|
||||
"@npm//shelljs",
|
||||
"@npm//yargs",
|
||||
],
|
||||
@ -29,6 +33,7 @@ ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"builder.spec.ts",
|
||||
"parse.spec.ts",
|
||||
"validate.spec.ts",
|
||||
],
|
||||
|
46
dev-infra/commit-message/builder.spec.ts
Normal file
46
dev-infra/commit-message/builder.spec.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @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 config from '../utils/config';
|
||||
import * as console from '../utils/console';
|
||||
|
||||
import {buildCommitMessage} from './builder';
|
||||
|
||||
|
||||
describe('commit message building:', () => {
|
||||
beforeEach(() => {
|
||||
// stub logging calls to prevent noise in test log
|
||||
spyOn(console, 'info').and.stub();
|
||||
// provide a configuration for DevInfra when loaded
|
||||
spyOn(config, 'getConfig').and.returnValue({
|
||||
commitMessage: {
|
||||
scopes: ['core'],
|
||||
}
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('creates a commit message with a scope', async () => {
|
||||
buildPromptResponseSpies('fix', 'core', 'This is a summary');
|
||||
|
||||
expect(await buildCommitMessage()).toMatch(/^fix\(core\): This is a summary/);
|
||||
});
|
||||
|
||||
it('creates a commit message without a scope', async () => {
|
||||
buildPromptResponseSpies('build', false, 'This is a summary');
|
||||
|
||||
expect(await buildCommitMessage()).toMatch(/^build: This is a summary/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Create spies to return the mocked selections from prompts. */
|
||||
function buildPromptResponseSpies(type: string, scope: string|false, summary: string) {
|
||||
spyOn(console, 'promptAutocomplete')
|
||||
.and.returnValues(Promise.resolve(type), Promise.resolve(scope));
|
||||
spyOn(console, 'promptInput').and.returnValue(Promise.resolve(summary));
|
||||
}
|
70
dev-infra/commit-message/builder.ts
Normal file
70
dev-infra/commit-message/builder.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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 {ListChoiceOptions} from 'inquirer';
|
||||
|
||||
import {info, promptAutocomplete, promptInput} from '../utils/console';
|
||||
|
||||
import {COMMIT_TYPES, CommitType, getCommitMessageConfig, ScopeRequirement} from './config';
|
||||
|
||||
/** Validate commit message at the provided file path. */
|
||||
export async function buildCommitMessage() {
|
||||
// TODO(josephperrott): Add support for skipping wizard with local untracked config file
|
||||
// TODO(josephperrott): Add default commit message information/commenting into generated messages
|
||||
info('Just a few questions to start building the commit message!');
|
||||
|
||||
/** The commit message type. */
|
||||
const type = await promptForCommitMessageType();
|
||||
/** The commit message scope. */
|
||||
const scope = await promptForCommitMessageScopeForType(type);
|
||||
/** The commit message summary. */
|
||||
const summary = await promptForCommitMessageSummary();
|
||||
|
||||
return `${type.name}${scope ? '(' + scope + ')' : ''}: ${summary}\n\n`;
|
||||
}
|
||||
|
||||
/** Prompts in the terminal for the commit message's type. */
|
||||
async function promptForCommitMessageType(): Promise<CommitType> {
|
||||
info('The type of change in the commit. Allows a reader to know the effect of the change,');
|
||||
info('whether it brings a new feature, adds additional testing, documents the `project, etc.');
|
||||
|
||||
/** List of commit type options for the autocomplete prompt. */
|
||||
const typeOptions: ListChoiceOptions[] =
|
||||
Object.values(COMMIT_TYPES).map(({description, name}) => {
|
||||
return {
|
||||
name: `${name} - ${description}`,
|
||||
value: name,
|
||||
short: name,
|
||||
};
|
||||
});
|
||||
/** The key of a commit message type, selected by the user via prompt. */
|
||||
const typeName = await promptAutocomplete('Select a type for the commit:', typeOptions);
|
||||
|
||||
return COMMIT_TYPES[typeName];
|
||||
}
|
||||
|
||||
/** Prompts in the terminal for the commit message's scope. */
|
||||
async function promptForCommitMessageScopeForType(type: CommitType): Promise<string|false> {
|
||||
// If the commit type's scope requirement is forbidden, return early.
|
||||
if (type.scope === ScopeRequirement.Forbidden) {
|
||||
info(`Skipping scope selection as the '${type.name}' type does not allow scopes`);
|
||||
return false;
|
||||
}
|
||||
/** Commit message configuration */
|
||||
const config = getCommitMessageConfig();
|
||||
|
||||
info('The area of the repository the changes in this commit most affects.');
|
||||
return await promptAutocomplete(
|
||||
'Select a scope for the commit:', config.commitMessage.scopes,
|
||||
type.scope === ScopeRequirement.Optional ? '<no scope>' : '');
|
||||
}
|
||||
|
||||
/** Prompts in the terminal for the commit message's summary. */
|
||||
async function promptForCommitMessageSummary(): Promise<string> {
|
||||
info('Provide a short summary of what the changes in the commit do');
|
||||
return await promptInput('Provide a short summary of the commit');
|
||||
}
|
@ -12,6 +12,7 @@ import {info} from '../utils/console';
|
||||
import {restoreCommitMessage} from './restore-commit-message';
|
||||
import {validateFile} from './validate-file';
|
||||
import {validateCommitRange} from './validate-range';
|
||||
import {runWizard} from './wizard';
|
||||
|
||||
/** Build the parser for the commit-message commands. */
|
||||
export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
@ -41,6 +42,23 @@ export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
args => {
|
||||
restoreCommitMessage(args['file-env-variable'][0], args['file-env-variable'][1] as any);
|
||||
})
|
||||
.command(
|
||||
'wizard <filePath> [source] [commitSha]', '', ((args: any) => {
|
||||
return args
|
||||
.positional(
|
||||
'filePath',
|
||||
{description: 'The file path to write the generated commit message into'})
|
||||
.positional('source', {
|
||||
choices: ['message', 'template', 'merge', 'squash', 'commit'],
|
||||
description: 'The source of the commit message as described here: ' +
|
||||
'https://git-scm.com/docs/githooks#_prepare_commit_msg'
|
||||
})
|
||||
.positional(
|
||||
'commitSha', {description: 'The commit sha if source is set to `commit`'});
|
||||
}),
|
||||
async (args: any) => {
|
||||
await runWizard(args);
|
||||
})
|
||||
.command(
|
||||
'pre-commit-validate', 'Validate the most recent commit message', {
|
||||
'file': {
|
||||
|
@ -39,36 +39,56 @@ export enum ScopeRequirement {
|
||||
|
||||
/** A commit type */
|
||||
export interface CommitType {
|
||||
description: string;
|
||||
name: string;
|
||||
scope: ScopeRequirement;
|
||||
}
|
||||
|
||||
/** The valid commit types for Angular commit messages. */
|
||||
export const COMMIT_TYPES: {[key: string]: CommitType} = {
|
||||
build: {
|
||||
name: 'build',
|
||||
description: 'Changes to local repository build system and tooling',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
},
|
||||
ci: {
|
||||
name: 'ci',
|
||||
description: 'Changes to CI configuration and CI specific tooling',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
},
|
||||
docs: {
|
||||
name: 'docs',
|
||||
description: 'Changes which exclusively affects documentation.',
|
||||
scope: ScopeRequirement.Optional,
|
||||
},
|
||||
feat: {
|
||||
name: 'feat',
|
||||
description: 'Creates a new feature',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
fix: {
|
||||
name: 'fix',
|
||||
description: 'Fixes a previously discovered failure/bug',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
perf: {
|
||||
name: 'perf',
|
||||
description: 'Improves performance without any change in functionality or API',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
refactor: {
|
||||
name: 'refactor',
|
||||
description: 'Refactor without any change in functionality or API (includes style changes)',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
release: {
|
||||
name: 'release',
|
||||
description: 'A release point in the repository',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
},
|
||||
test: {
|
||||
name: 'test',
|
||||
description: 'Improvements or corrections made to the project\'s test suite',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
};
|
||||
|
@ -82,4 +82,24 @@ describe('commit message parsing:', () => {
|
||||
const message2 = buildCommitMessage({prefix: 'squash! '});
|
||||
expect(parseCommitMessage(message2).isSquash).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores comment lines', () => {
|
||||
const message = buildCommitMessage({
|
||||
prefix: '# This is a comment line before the header.\n' +
|
||||
'## This is another comment line before the headers.\n',
|
||||
body: '# This is a comment line befor the body.\n' +
|
||||
'This is line 1 of the actual body.\n' +
|
||||
'## This is another comment line inside the body.\n' +
|
||||
'This is line 2 of the actual body (and it also contains a # but it not a comment).\n' +
|
||||
'### This is yet another comment line after the body.\n',
|
||||
});
|
||||
const parsedMessage = parseCommitMessage(message);
|
||||
|
||||
expect(parsedMessage.header)
|
||||
.toBe(`${commitValues.type}(${commitValues.scope}): ${commitValues.summary}`);
|
||||
expect(parsedMessage.body)
|
||||
.toBe(
|
||||
'This is line 1 of the actual body.\n' +
|
||||
'This is line 2 of the actual body (and it also contains a # but it not a comment).\n');
|
||||
});
|
||||
});
|
||||
|
@ -36,6 +36,10 @@ const COMMIT_BODY_RE = /^.*\n\n([\s\S]*)$/;
|
||||
|
||||
/** Parse a full commit message into its composite parts. */
|
||||
export function parseCommitMessage(commitMsg: string): ParsedCommitMessage {
|
||||
// Ignore comments (i.e. lines starting with `#`). Comments are automatically removed by git and
|
||||
// should not be considered part of the final commit message.
|
||||
commitMsg = commitMsg.split('\n').filter(line => !line.startsWith('#')).join('\n');
|
||||
|
||||
let header = '';
|
||||
let body = '';
|
||||
let bodyWithoutLinking = '';
|
||||
|
43
dev-infra/commit-message/wizard.ts
Normal file
43
dev-infra/commit-message/wizard.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @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 {writeFileSync} from 'fs';
|
||||
|
||||
import {info} from '../utils/console';
|
||||
|
||||
import {buildCommitMessage} from './builder';
|
||||
|
||||
/**
|
||||
* The source triggering the git commit message creation.
|
||||
* As described in: https://git-scm.com/docs/githooks#_prepare_commit_msg
|
||||
*/
|
||||
export type PrepareCommitMsgHookSource = 'message'|'template'|'merge'|'squash'|'commit';
|
||||
|
||||
/** The default commit message used if the wizard does not procude a commit message. */
|
||||
const defaultCommitMessage = `<type>(<scope>): <summary>
|
||||
|
||||
# <Describe the motivation behind this change - explain WHY you are making this change. Wrap all
|
||||
# lines at 100 characters.>\n\n`;
|
||||
|
||||
export async function runWizard(
|
||||
args: {filePath: string, source?: PrepareCommitMsgHookSource, commitSha?: string}) {
|
||||
// TODO(josephperrott): Add support for skipping wizard with local untracked config file
|
||||
|
||||
if (args.source !== undefined) {
|
||||
info(`Skipping commit message wizard due because the commit was created via '${
|
||||
args.source}' source`);
|
||||
process.exitCode = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the default commit message to be updated if the user cancels out of the wizard in progress
|
||||
writeFileSync(args.filePath, defaultCommitMessage);
|
||||
|
||||
/** The generated commit message. */
|
||||
const commitMessage = await buildCommitMessage();
|
||||
writeFileSync(args.filePath, commitMessage);
|
||||
}
|
@ -6,6 +6,7 @@ ts_library(
|
||||
module_name = "@angular/dev-infra-private/pr",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/pr/checkout",
|
||||
"//dev-infra/pr/discover-new-conflicts",
|
||||
"//dev-infra/pr/merge",
|
||||
"//dev-infra/pr/rebase",
|
||||
|
13
dev-infra/pr/checkout/BUILD.bazel
Normal file
13
dev-infra/pr/checkout/BUILD.bazel
Normal file
@ -0,0 +1,13 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "checkout",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "@angular/dev-infra-private/pr/checkout",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/pr/common",
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/yargs",
|
||||
],
|
||||
)
|
36
dev-infra/pr/checkout/cli.ts
Normal file
36
dev-infra/pr/checkout/cli.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @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 {checkOutPullRequestLocally} from '../common/checkout-pr';
|
||||
|
||||
export interface CheckoutOptions {
|
||||
prNumber: number;
|
||||
githubToken: string;
|
||||
}
|
||||
|
||||
/** Builds the checkout pull request command. */
|
||||
function builder(yargs: Argv) {
|
||||
return addGithubTokenFlag(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
}
|
||||
|
||||
/** Handles the checkout pull request command. */
|
||||
async function handler({prNumber, githubToken}: Arguments<CheckoutOptions>) {
|
||||
const prCheckoutOptions = {allowIfMaintainerCannotModify: true, branchName: `pr-${prNumber}`};
|
||||
await checkOutPullRequestLocally(prNumber, githubToken, prCheckoutOptions);
|
||||
}
|
||||
|
||||
/** yargs command module for checking out a PR */
|
||||
export const CheckoutCommandModule: CommandModule<{}, CheckoutOptions> = {
|
||||
handler,
|
||||
builder,
|
||||
command: 'checkout <pr-number>',
|
||||
describe: 'Checkout a PR from the upstream repo',
|
||||
};
|
@ -8,6 +8,7 @@
|
||||
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import {CheckoutCommandModule} from './checkout/cli';
|
||||
import {buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand} from './discover-new-conflicts/cli';
|
||||
import {buildMergeCommand, handleMergeCommand} from './merge/cli';
|
||||
import {buildRebaseCommand, handleRebaseCommand} from './rebase/cli';
|
||||
@ -24,7 +25,8 @@ export function buildPrParser(localYargs: yargs.Argv) {
|
||||
buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand)
|
||||
.command(
|
||||
'rebase <pr-number>', 'Rebase a pending PR and push the rebased commits back to Github',
|
||||
buildRebaseCommand, handleRebaseCommand);
|
||||
buildRebaseCommand, handleRebaseCommand)
|
||||
.command(CheckoutCommandModule);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
12
dev-infra/pr/common/BUILD.bazel
Normal file
12
dev-infra/pr/common/BUILD.bazel
Normal file
@ -0,0 +1,12 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "common",
|
||||
srcs = glob(["*.ts"]),
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/node",
|
||||
"@npm//typed-graphqlify",
|
||||
],
|
||||
)
|
135
dev-infra/pr/common/checkout-pr.ts
Normal file
135
dev-infra/pr/common/checkout-pr.ts
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @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 {types as graphQLTypes} from 'typed-graphqlify';
|
||||
import {URL} from 'url';
|
||||
|
||||
import {info} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {getPr} from '../../utils/github';
|
||||
|
||||
/* GraphQL schema for the response body for a pending PR. */
|
||||
const PR_SCHEMA = {
|
||||
state: graphQLTypes.string,
|
||||
maintainerCanModify: graphQLTypes.boolean,
|
||||
viewerDidAuthor: graphQLTypes.boolean,
|
||||
headRefOid: graphQLTypes.string,
|
||||
headRef: {
|
||||
name: graphQLTypes.string,
|
||||
repository: {
|
||||
url: graphQLTypes.string,
|
||||
nameWithOwner: graphQLTypes.string,
|
||||
},
|
||||
},
|
||||
baseRef: {
|
||||
name: graphQLTypes.string,
|
||||
repository: {
|
||||
url: graphQLTypes.string,
|
||||
nameWithOwner: graphQLTypes.string,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export class UnexpectedLocalChangesError extends Error {
|
||||
constructor(m: string) {
|
||||
super(m);
|
||||
Object.setPrototypeOf(this, UnexpectedLocalChangesError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class MaintainerModifyAccessError extends Error {
|
||||
constructor(m: string) {
|
||||
super(m);
|
||||
Object.setPrototypeOf(this, MaintainerModifyAccessError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/** Options for checking out a PR */
|
||||
export interface PullRequestCheckoutOptions {
|
||||
/** Whether the PR should be checked out if the maintainer cannot modify. */
|
||||
allowIfMaintainerCannotModify?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebase the provided PR onto its merge target branch, and push up the resulting
|
||||
* commit to the PRs repository.
|
||||
*/
|
||||
export async function checkOutPullRequestLocally(
|
||||
prNumber: number, githubToken: string, opts: PullRequestCheckoutOptions = {}) {
|
||||
/** Authenticated Git client for git and Github interactions. */
|
||||
const git = new GitClient(githubToken);
|
||||
|
||||
// In order to preserve local changes, checkouts cannot occur if local changes are present in the
|
||||
// git environment. Checked before retrieving the PR to fail fast.
|
||||
if (git.hasLocalChanges()) {
|
||||
throw new UnexpectedLocalChangesError('Unable to checkout PR due to uncommitted changes.');
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch or revision originally checked out before this method performed
|
||||
* any Git operations that may change the working branch.
|
||||
*/
|
||||
const previousBranchOrRevision = git.getCurrentBranchOrRevision();
|
||||
/* The PR information from Github. */
|
||||
const pr = await getPr(PR_SCHEMA, prNumber, git);
|
||||
/** The branch name of the PR from the repository the PR came from. */
|
||||
const headRefName = pr.headRef.name;
|
||||
/** The full ref for the repository and branch the PR came from. */
|
||||
const fullHeadRef = `${pr.headRef.repository.nameWithOwner}:${headRefName}`;
|
||||
/** The full URL path of the repository the PR came from with github token as authentication. */
|
||||
const headRefUrl = addAuthenticationToUrl(pr.headRef.repository.url, githubToken);
|
||||
// Note: Since we use a detached head for rebasing the PR and therefore do not have
|
||||
// remote-tracking branches configured, we need to set our expected ref and SHA. This
|
||||
// allows us to use `--force-with-lease` for the detached head while ensuring that we
|
||||
// never accidentally override upstream changes that have been pushed in the meanwhile.
|
||||
// See:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaseltrefnamegtltexpectgt
|
||||
/** Flag for a force push with leage back to upstream. */
|
||||
const forceWithLeaseFlag = `--force-with-lease=${headRefName}:${pr.headRefOid}`;
|
||||
|
||||
// If the PR does not allow maintainers to modify it, exit as the rebased PR cannot
|
||||
// be pushed up.
|
||||
if (!pr.maintainerCanModify && !pr.viewerDidAuthor && !opts.allowIfMaintainerCannotModify) {
|
||||
throw new MaintainerModifyAccessError('PR is not set to allow maintainers to modify the PR');
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch the branch at the commit of the PR, and check it out in a detached state.
|
||||
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
|
||||
git.run(['fetch', headRefUrl, headRefName]);
|
||||
git.run(['checkout', '--detach', 'FETCH_HEAD']);
|
||||
} catch (e) {
|
||||
git.checkout(previousBranchOrRevision, true);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Pushes the current local branch to the PR on the upstream repository.
|
||||
*
|
||||
* @returns true If the command did not fail causing a GitCommandError to be thrown.
|
||||
* @throws GitCommandError Thrown when the push back to upstream fails.
|
||||
*/
|
||||
pushToUpstream: (): true => {
|
||||
git.run(['push', headRefUrl, `HEAD:${headRefName}`, forceWithLeaseFlag]);
|
||||
return true;
|
||||
},
|
||||
/** Restores the state of the local repository to before the PR checkout occured. */
|
||||
resetGitState: (): boolean => {
|
||||
return git.checkout(previousBranchOrRevision, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Adds the provided token as username to the provided url. */
|
||||
function addAuthenticationToUrl(urlString: string, token: string) {
|
||||
const url = new URL(urlString);
|
||||
url.username = token;
|
||||
return url.toString();
|
||||
}
|
@ -72,7 +72,7 @@ export async function discoverNewConflictsForPr(
|
||||
|
||||
info(`Requesting pending PRs from Github`);
|
||||
/** List of PRs from github currently known as mergable. */
|
||||
const allPendingPRs = (await getPendingPrs(PR_SCHEMA, config.github)).map(processPr);
|
||||
const allPendingPRs = (await getPendingPrs(PR_SCHEMA, git)).map(processPr);
|
||||
/** The PR which is being checked against. */
|
||||
const requestedPr = allPendingPRs.find(pr => pr.number === newPrNumber);
|
||||
if (requestedPr === undefined) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export async function rebasePr(
|
||||
*/
|
||||
const previousBranchOrRevision = git.getCurrentBranchOrRevision();
|
||||
/* Get the PR information from Github. */
|
||||
const pr = await getPr(PR_SCHEMA, prNumber, config.github);
|
||||
const pr = await getPr(PR_SCHEMA, prNumber, git);
|
||||
|
||||
const headRefName = pr.headRef.name;
|
||||
const baseRefName = pr.baseRef.name;
|
||||
|
@ -12,13 +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,13 +7,19 @@
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {prompt} from 'inquirer';
|
||||
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> {
|
||||
@ -26,6 +32,52 @@ export async function promptConfirm(message: string, defaultValue = false): Prom
|
||||
.result;
|
||||
}
|
||||
|
||||
/** Prompts the user to select an option from a filterable autocomplete list. */
|
||||
export async function promptAutocomplete(
|
||||
message: string, choices: (string|ListChoiceOptions)[]): Promise<string>;
|
||||
/**
|
||||
* Prompts the user to select an option from a filterable autocomplete list, with an option to
|
||||
* choose no value.
|
||||
*/
|
||||
export async function promptAutocomplete(
|
||||
message: string, choices: (string|ListChoiceOptions)[],
|
||||
noChoiceText?: string): Promise<string|false>;
|
||||
export async function promptAutocomplete(
|
||||
message: string, choices: (string|ListChoiceOptions)[],
|
||||
noChoiceText?: string): Promise<string|false> {
|
||||
// Creates a local prompt module with an autocomplete prompt type.
|
||||
const prompt = createPromptModule({}).registerPrompt('autocomplete', inquirerAutocomplete);
|
||||
if (noChoiceText) {
|
||||
choices = [noChoiceText, ...choices];
|
||||
}
|
||||
// `prompt` must be cast as `any` as the autocomplete typings are not available.
|
||||
const result = (await (prompt as any)({
|
||||
type: 'autocomplete',
|
||||
name: 'result',
|
||||
message,
|
||||
source: (_: any, input: string) => {
|
||||
if (!input) {
|
||||
return Promise.resolve(choices);
|
||||
}
|
||||
return Promise.resolve(choices.filter(choice => {
|
||||
if (typeof choice === 'string') {
|
||||
return choice.includes(input);
|
||||
}
|
||||
return choice.name!.includes(input);
|
||||
}));
|
||||
}
|
||||
})).result;
|
||||
if (result === noChoiceText) {
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Prompts the user for one line of input. */
|
||||
export async function promptInput(message: string): Promise<string> {
|
||||
return (await prompt<{result: string}>({type: 'input', name: 'result', message})).result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported levels for logging functions.
|
||||
*
|
||||
@ -93,6 +145,7 @@ function runConsoleCommand(loadCommand: () => Function, logLevel: LOG_LEVELS, ..
|
||||
if (getLogLevel() >= logLevel) {
|
||||
loadCommand()(...text);
|
||||
}
|
||||
printToLogFile(logLevel, ...text);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,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('');
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export class GithubApiRequestError extends Error {
|
||||
**/
|
||||
export class GithubClient extends Octokit {
|
||||
/** The Github GraphQL (v4) API. */
|
||||
graqhql: GithubGraphqlClient;
|
||||
graphql: GithubGraphqlClient;
|
||||
|
||||
/** The current user based on checking against the Github API. */
|
||||
private _currentUser: string|null = null;
|
||||
@ -42,7 +42,7 @@ export class GithubClient extends Octokit {
|
||||
});
|
||||
|
||||
// Create authenticated graphql client.
|
||||
this.graqhql = new GithubGraphqlClient(token);
|
||||
this.graphql = new GithubGraphqlClient(token);
|
||||
}
|
||||
|
||||
/** Retrieve the login of the current user from Github. */
|
||||
@ -51,7 +51,7 @@ export class GithubClient extends Octokit {
|
||||
if (this._currentUser !== null) {
|
||||
return this._currentUser;
|
||||
}
|
||||
const result = await this.graqhql.query({
|
||||
const result = await this.graphql.query({
|
||||
viewer: {
|
||||
login: types.string,
|
||||
}
|
||||
@ -80,7 +80,7 @@ class GithubGraphqlClient {
|
||||
// Set the default headers to include authorization with the provided token for all
|
||||
// graphQL calls.
|
||||
if (token) {
|
||||
this.graqhql.defaults({headers: {authorization: `token ${token}`}});
|
||||
this.graqhql = this.graqhql.defaults({headers: {authorization: `token ${token}`}});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(' ')));
|
||||
@ -150,6 +150,25 @@ export class GitClient {
|
||||
return value.replace(this._githubTokenRegex, '<TOKEN>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks out a requested branch or revision, optionally cleaning the state of the repository
|
||||
* before attempting the checking. Returns a boolean indicating whether the branch or revision
|
||||
* was cleanly checked out.
|
||||
*/
|
||||
checkout(branchOrRevision: string, cleanState: boolean): boolean {
|
||||
if (cleanState) {
|
||||
// Abort any outstanding ams.
|
||||
this.runGraceful(['am', '--abort'], {stdio: 'ignore'});
|
||||
// Abort any outstanding cherry-picks.
|
||||
this.runGraceful(['cherry-pick', '--abort'], {stdio: 'ignore'});
|
||||
// Abort any outstanding rebases.
|
||||
this.runGraceful(['rebase', '--abort'], {stdio: 'ignore'});
|
||||
// Clear any changes in the current repo.
|
||||
this.runGraceful(['reset', '--hard'], {stdio: 'ignore'});
|
||||
}
|
||||
return this.runGraceful(['checkout', branchOrRevision], {stdio: 'ignore'}).status === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the GitClient instance is using a token with permissions for the all of the
|
||||
* provided OAuth scopes.
|
||||
|
@ -6,29 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {graphql as unauthenticatedGraphql} from '@octokit/graphql';
|
||||
import {params, types} from 'typed-graphqlify';
|
||||
|
||||
import {params, query as graphqlQuery, types} from 'typed-graphqlify';
|
||||
import {NgDevConfig} from './config';
|
||||
|
||||
/** The configuration required for github interactions. */
|
||||
type GithubConfig = NgDevConfig['github'];
|
||||
|
||||
/**
|
||||
* Authenticated instance of Github GraphQl API service, relies on a
|
||||
* personal access token being available in the TOKEN environment variable.
|
||||
*/
|
||||
const graphql = unauthenticatedGraphql.defaults({
|
||||
headers: {
|
||||
// TODO(josephperrott): Remove reference to TOKEN environment variable as part of larger
|
||||
// effort to migrate to expecting tokens via GITHUB_ACCESS_TOKEN environment variables.
|
||||
authorization: `token ${process.env.TOKEN || process.env.GITHUB_ACCESS_TOKEN}`,
|
||||
}
|
||||
});
|
||||
import {GitClient} from './git';
|
||||
|
||||
/** Get a PR from github */
|
||||
export async function getPr<PrSchema>(
|
||||
prSchema: PrSchema, prNumber: number, {owner, name}: GithubConfig) {
|
||||
export async function getPr<PrSchema>(prSchema: PrSchema, prNumber: number, git: GitClient) {
|
||||
/** The owner and name of the repository */
|
||||
const {owner, name} = git.remoteConfig;
|
||||
/** The GraphQL query object to get a the PR */
|
||||
const PR_QUERY = params(
|
||||
{
|
||||
$number: 'Int!', // The PR number
|
||||
@ -41,14 +27,15 @@ export async function getPr<PrSchema>(
|
||||
})
|
||||
});
|
||||
|
||||
const result =
|
||||
await graphql(graphqlQuery(PR_QUERY), {number: prNumber, owner, name}) as typeof PR_QUERY;
|
||||
const result = (await git.github.graphql.query(PR_QUERY, {number: prNumber, owner, name}));
|
||||
return result.repository.pullRequest;
|
||||
}
|
||||
|
||||
/** Get all pending PRs from github */
|
||||
export async function getPendingPrs<PrSchema>(prSchema: PrSchema, {owner, name}: GithubConfig) {
|
||||
// The GraphQL query object to get a page of pending PRs
|
||||
export async function getPendingPrs<PrSchema>(prSchema: PrSchema, git: GitClient) {
|
||||
/** The owner and name of the repository */
|
||||
const {owner, name} = git.remoteConfig;
|
||||
/** The GraphQL query object to get a page of pending PRs */
|
||||
const PRS_QUERY = params(
|
||||
{
|
||||
$first: 'Int', // How many entries to get with each request
|
||||
@ -73,36 +60,22 @@ export async function getPendingPrs<PrSchema>(prSchema: PrSchema, {owner, name}:
|
||||
}),
|
||||
})
|
||||
});
|
||||
const query = graphqlQuery('members', PRS_QUERY);
|
||||
|
||||
/**
|
||||
* Gets the query and queryParams for a specific page of entries.
|
||||
*/
|
||||
const queryBuilder = (count: number, cursor?: string) => {
|
||||
return {
|
||||
query,
|
||||
params: {
|
||||
after: cursor || null,
|
||||
first: count,
|
||||
owner,
|
||||
name,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// The current cursor
|
||||
/** The current cursor */
|
||||
let cursor: string|undefined;
|
||||
// If an additional page of members is expected
|
||||
/** If an additional page of members is expected */
|
||||
let hasNextPage = true;
|
||||
// Array of pending PRs
|
||||
/** Array of pending PRs */
|
||||
const prs: Array<PrSchema> = [];
|
||||
|
||||
// For each page of the response, get the page and add it to the
|
||||
// list of PRs
|
||||
// For each page of the response, get the page and add it to the list of PRs
|
||||
while (hasNextPage) {
|
||||
const {query, params} = queryBuilder(100, cursor);
|
||||
const results = await graphql(query, params) as typeof PRS_QUERY;
|
||||
|
||||
const params = {
|
||||
after: cursor || null,
|
||||
first: 100,
|
||||
owner,
|
||||
name,
|
||||
};
|
||||
const results = await git.github.graphql.query(PRS_QUERY, params) as typeof PRS_QUERY;
|
||||
prs.push(...results.repository.pullRequests.nodes);
|
||||
hasNextPage = results.repository.pullRequests.pageInfo.hasNextPage;
|
||||
cursor = results.repository.pullRequests.pageInfo.endCursor;
|
||||
|
17
dev-infra/utils/inquirer-autocomplete-typings.d.ts
vendored
Normal file
17
dev-infra/utils/inquirer-autocomplete-typings.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// inquirer-autocomplete-prompt doesn't provide types and no types are made available via
|
||||
// DefinitelyTyped.
|
||||
declare module "inquirer-autocomplete-prompt" {
|
||||
|
||||
import {registerPrompt} from 'inquirer';
|
||||
|
||||
let AutocompletePrompt: Parameters<typeof registerPrompt>[1];
|
||||
export = AutocompletePrompt;
|
||||
}
|
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';
|
2
goldens/public-api/common/common.d.ts
vendored
2
goldens/public-api/common/common.d.ts
vendored
@ -216,7 +216,7 @@ export declare class NgComponentOutlet implements OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
export declare class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {
|
||||
set ngForOf(ngForOf: (U & NgIterable<T>) | undefined | null);
|
||||
set ngForOf(ngForOf: U & NgIterable<T> | undefined | null);
|
||||
set ngForTemplate(value: TemplateRef<NgForOfContext<T, U>>);
|
||||
set ngForTrackBy(fn: TrackByFunction<T>);
|
||||
get ngForTrackBy(): TrackByFunction<T>;
|
||||
|
2
goldens/public-api/elements/elements.d.ts
vendored
2
goldens/public-api/elements/elements.d.ts
vendored
@ -2,7 +2,7 @@ export declare function createCustomElement<P>(component: Type<any>, config: NgE
|
||||
|
||||
export declare abstract class NgElement extends HTMLElement {
|
||||
protected ngElementEventsSubscription: Subscription | null;
|
||||
protected ngElementStrategy: NgElementStrategy;
|
||||
protected abstract ngElementStrategy: NgElementStrategy;
|
||||
abstract attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string, namespace?: string): void;
|
||||
abstract connectedCallback(): void;
|
||||
abstract disconnectedCallback(): void;
|
||||
|
@ -85,6 +85,12 @@ INTEGRATION_TESTS = {
|
||||
# root @npm//typescript package.
|
||||
"pinned_npm_packages": ["typescript"],
|
||||
},
|
||||
"typings_test_ts40": {
|
||||
# Special case for `typings_test_ts40` test as we want to pin
|
||||
# `typescript` at version 4.0.x for that test and not link to the
|
||||
# root @npm//typescript package.
|
||||
"pinned_npm_packages": ["typescript"],
|
||||
},
|
||||
}
|
||||
|
||||
[
|
||||
|
@ -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(['...']));
|
||||
}
|
||||
|
71
integration/typings_test_ts40/include-all.ts
Normal file
71
integration/typings_test_ts40/include-all.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @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 animations from '@angular/animations';
|
||||
import * as animationsBrowser from '@angular/animations/browser';
|
||||
import * as animationsBrowserTesting from '@angular/animations/browser/testing';
|
||||
import * as common from '@angular/common';
|
||||
import * as commonHttp from '@angular/common/http';
|
||||
import * as commonTesting from '@angular/common/testing';
|
||||
import * as commonHttpTesting from '@angular/common/testing';
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as core from '@angular/core';
|
||||
import * as coreTesting from '@angular/core/testing';
|
||||
import * as elements from '@angular/elements';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynamic/testing';
|
||||
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as platformWebworker from '@angular/platform-webworker';
|
||||
import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as routerUpgrade from '@angular/router/upgrade';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
import * as upgradeStatic from '@angular/upgrade/static';
|
||||
import * as upgradeTesting from '@angular/upgrade/static/testing';
|
||||
|
||||
export default {
|
||||
animations,
|
||||
animationsBrowser,
|
||||
animationsBrowserTesting,
|
||||
common,
|
||||
commonTesting,
|
||||
commonHttp,
|
||||
commonHttpTesting,
|
||||
compiler,
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
elements,
|
||||
forms,
|
||||
platformBrowser,
|
||||
platformBrowserTesting,
|
||||
platformBrowserDynamic,
|
||||
platformBrowserDynamicTesting,
|
||||
platformBrowserAnimations,
|
||||
platformServer,
|
||||
platformServerTesting,
|
||||
platformWebworker,
|
||||
platformWebworkerDynamic,
|
||||
router,
|
||||
routerTesting,
|
||||
routerUpgrade,
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
upgradeStatic,
|
||||
upgradeTesting,
|
||||
};
|
30
integration/typings_test_ts40/package.json
Normal file
30
integration/typings_test_ts40/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 4.0 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/elements": "file:../../dist/packages-dist/elements",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker",
|
||||
"@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "file:../../node_modules/@types/jasmine",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"typescript": "4.0.2",
|
||||
"zone.js": "file:../../dist/zone.js-dist/zone.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tsc"
|
||||
}
|
||||
}
|
26
integration/typings_test_ts40/tsconfig.json
Normal file
26
integration/typings_test_ts40/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.iterable",
|
||||
"es2015.promise"
|
||||
],
|
||||
"types": [],
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
"node_modules/@types/jasmine/index.d.ts"
|
||||
]
|
||||
}
|
18
package.json
18
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "10.1.0-next.6",
|
||||
"version": "10.1.0",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
@ -76,18 +76,19 @@
|
||||
"@types/diff": "^3.5.1",
|
||||
"@types/fs-extra": "4.0.2",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/inquirer": "^7.3.0",
|
||||
"@types/jasmine": "3.5.10",
|
||||
"@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",
|
||||
@ -149,10 +150,10 @@
|
||||
"terser": "^4.4.0",
|
||||
"tsickle": "0.38.1",
|
||||
"tslib": "^2.0.0",
|
||||
"tslint": "6.0.0",
|
||||
"typescript": "~3.9.5",
|
||||
"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.",
|
||||
@ -179,8 +180,9 @@
|
||||
"glob": "7.1.2",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-conventional-changelog": "^2.0.3",
|
||||
"husky": "^4.2.3",
|
||||
"inquirer": "^7.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"inquirer": "^7.3.3",
|
||||
"inquirer-autocomplete-prompt": "^1.0.2",
|
||||
"jpm": "1.3.1",
|
||||
"karma-browserstack-launcher": "^1.3.0",
|
||||
"karma-sauce-launcher": "^2.0.2",
|
||||
|
@ -34,7 +34,7 @@
|
||||
"@angular/compiler-cli": "0.0.0-PLACEHOLDER",
|
||||
"@bazel/typescript": ">=1.0.0",
|
||||
"terser": "^4.3.1",
|
||||
"typescript": ">=3.9 <4.0",
|
||||
"typescript": ">=3.9 <4.1",
|
||||
"rollup": ">=1.20.0",
|
||||
"rollup-plugin-commonjs": ">=9.0.0",
|
||||
"rollup-plugin-node-resolve": ">=4.2.0",
|
||||
|
@ -27,7 +27,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "0.0.0-PLACEHOLDER",
|
||||
"typescript": ">=3.9 <4.0"
|
||||
"typescript": ">=3.9 <4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
|
@ -476,13 +476,16 @@ export class Evaluator {
|
||||
return recordEntry(typeReference, node);
|
||||
case ts.SyntaxKind.UnionType:
|
||||
const unionType = <ts.UnionTypeNode>node;
|
||||
|
||||
// Remove null and undefined from the list of unions.
|
||||
const references = unionType.types
|
||||
.filter(
|
||||
n => n.kind != ts.SyntaxKind.NullKeyword &&
|
||||
n.kind != ts.SyntaxKind.UndefinedKeyword)
|
||||
.map(n => this.evaluateNode(n));
|
||||
// TODO(alan-agius4): remove `n.kind !== ts.SyntaxKind.NullKeyword` when
|
||||
// TS 3.9 support is dropped. In TS 4.0 NullKeyword is a child of LiteralType.
|
||||
const references =
|
||||
unionType.types
|
||||
.filter(
|
||||
n => n.kind !== ts.SyntaxKind.NullKeyword &&
|
||||
n.kind !== ts.SyntaxKind.UndefinedKeyword &&
|
||||
!(ts.isLiteralTypeNode(n) && n.literal.kind === ts.SyntaxKind.NullKeyword))
|
||||
.map(n => this.evaluateNode(n));
|
||||
|
||||
// The remmaining reference must be the same. If two have type arguments consider them
|
||||
// different even if the type arguments are the same.
|
||||
|
@ -62,8 +62,8 @@ export class Symbols {
|
||||
// even if the `SourceFile` was not type checked (which looks for `SourceFile`
|
||||
// in the parent chain). This doesn't damage the node as the binder unconditionally
|
||||
// sets the parent.
|
||||
externalReference.expression.parent = externalReference;
|
||||
externalReference.parent = this.sourceFile as any;
|
||||
(externalReference.expression.parent as ts.Node) = externalReference;
|
||||
(externalReference.parent as ts.Node) = this.sourceFile;
|
||||
}
|
||||
const from = stripQuotes(externalReference.expression.getText());
|
||||
symbols.set(
|
||||
@ -83,8 +83,8 @@ export class Symbols {
|
||||
}
|
||||
if (!importDecl.moduleSpecifier.parent) {
|
||||
// See note above in the `ImportEqualDeclaration` case.
|
||||
importDecl.moduleSpecifier.parent = importDecl;
|
||||
importDecl.parent = this.sourceFile;
|
||||
(importDecl.moduleSpecifier.parent as ts.Node) = importDecl;
|
||||
(importDecl.parent as ts.Node) = this.sourceFile;
|
||||
}
|
||||
const from = stripQuotes(importDecl.moduleSpecifier.getText());
|
||||
if (importDecl.importClause.name) {
|
||||
|
@ -28,7 +28,7 @@ export function generateSetClassMetadataCall(
|
||||
if (!reflection.isClass(clazz)) {
|
||||
return null;
|
||||
}
|
||||
const id = ts.updateIdentifier(reflection.getAdjacentNameOfClass(clazz));
|
||||
const id = reflection.getAdjacentNameOfClass(clazz);
|
||||
|
||||
// Reflect over the class decorators. If none are present, or those that are aren't from
|
||||
// Angular, then return null. Otherwise, turn them into metadata.
|
||||
|
@ -69,7 +69,7 @@ runInEachFileSystem(() => {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
});
|
||||
const fooClause = getDeclaration(program, _('/test.ts'), 'Foo', ts.isImportClause);
|
||||
const fooId = ts.updateIdentifier(fooClause.name!);
|
||||
const fooId = fooClause.name!;
|
||||
const fooDecl = fooClause.parent;
|
||||
|
||||
const tracker = new DefaultImportTracker();
|
||||
|
@ -20,22 +20,26 @@ export function extractReferencesFromType(
|
||||
if (!ts.isTupleTypeNode(def)) {
|
||||
return [];
|
||||
}
|
||||
return def.elementTypes.map(element => {
|
||||
if (!ts.isTypeQueryNode(element)) {
|
||||
throw new Error(`Expected TypeQueryNode: ${nodeDebugInfo(element)}`);
|
||||
}
|
||||
const type = element.exprName;
|
||||
const {node, from} = reflectTypeEntityToDeclaration(type, checker);
|
||||
if (!isNamedClassDeclaration(node)) {
|
||||
throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`);
|
||||
}
|
||||
const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
|
||||
if (specifier !== null) {
|
||||
return new Reference(node, {specifier, resolutionContext});
|
||||
} else {
|
||||
return new Reference(node);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(alan-agius4): remove `def.elementTypes` and casts when TS 3.9 support is dropped and G3 is
|
||||
// using TS 4.0.
|
||||
return (((def as any).elements || (def as any).elementTypes) as ts.NodeArray<ts.TypeNode>)
|
||||
.map(element => {
|
||||
if (!ts.isTypeQueryNode(element)) {
|
||||
throw new Error(`Expected TypeQueryNode: ${nodeDebugInfo(element)}`);
|
||||
}
|
||||
const type = element.exprName;
|
||||
const {node, from} = reflectTypeEntityToDeclaration(type, checker);
|
||||
if (!isNamedClassDeclaration(node)) {
|
||||
throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`);
|
||||
}
|
||||
const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
|
||||
if (specifier !== null) {
|
||||
return new Reference(node, {specifier, resolutionContext});
|
||||
} else {
|
||||
return new Reference(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function readStringType(type: ts.TypeNode): string|null {
|
||||
@ -69,12 +73,15 @@ export function readStringArrayType(type: ts.TypeNode): string[] {
|
||||
return [];
|
||||
}
|
||||
const res: string[] = [];
|
||||
type.elementTypes.forEach(el => {
|
||||
if (!ts.isLiteralTypeNode(el) || !ts.isStringLiteral(el.literal)) {
|
||||
return;
|
||||
}
|
||||
res.push(el.literal.text);
|
||||
});
|
||||
// TODO(alan-agius4): remove `def.elementTypes` and casts when TS 3.9 support is dropped and G3 is
|
||||
// using TS 4.0.
|
||||
(((type as any).elements || (type as any).elementTypes) as ts.NodeArray<ts.TypeNode>)
|
||||
.forEach(el => {
|
||||
if (!ts.isLiteralTypeNode(el) || !ts.isStringLiteral(el.literal)) {
|
||||
return;
|
||||
}
|
||||
res.push(el.literal.text);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -35,8 +35,9 @@ export function typeToValue(
|
||||
|
||||
const {local, decl} = symbols;
|
||||
// It's only valid to convert a type reference to a value reference if the type actually
|
||||
// has a value declaration associated with it.
|
||||
if (decl.valueDeclaration === undefined) {
|
||||
// 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]);
|
||||
}
|
||||
|
||||
@ -58,9 +59,7 @@ export function typeToValue(
|
||||
|
||||
return {
|
||||
kind: TypeValueReferenceKind.LOCAL,
|
||||
// Copying the name here ensures the generated references will be correctly transformed
|
||||
// along with the import.
|
||||
expression: ts.updateIdentifier(firstDecl.name),
|
||||
expression: firstDecl.name,
|
||||
defaultImportStatement: firstDecl.parent,
|
||||
};
|
||||
} else if (ts.isImportSpecifier(firstDecl)) {
|
||||
|
@ -64,7 +64,11 @@ export class TypeScriptReflectionHost implements ReflectionHost {
|
||||
// optional tokes that don't have providers.
|
||||
if (typeNode && ts.isUnionTypeNode(typeNode)) {
|
||||
let childTypeNodes = typeNode.types.filter(
|
||||
childTypeNode => childTypeNode.kind !== ts.SyntaxKind.NullKeyword);
|
||||
// TODO(alan-agius4): remove `childTypeNode.kind !== ts.SyntaxKind.NullKeyword` when
|
||||
// TS 3.9 support is dropped. In TS 4.0 NullKeyword is a child of LiteralType.
|
||||
childTypeNode => childTypeNode.kind !== ts.SyntaxKind.NullKeyword &&
|
||||
!(ts.isLiteralTypeNode(childTypeNode) &&
|
||||
childTypeNode.literal.kind === ts.SyntaxKind.NullKeyword));
|
||||
|
||||
if (childTypeNodes.length === 1) {
|
||||
typeNode = childTypeNodes[0];
|
||||
|
@ -128,8 +128,6 @@ function transformFactorySourceFile(
|
||||
|
||||
const {moduleSymbols, sourceFilePath} = factoryMap.get(file.fileName)!;
|
||||
|
||||
file = ts.getMutableClone(file);
|
||||
|
||||
// Not every exported factory statement is valid. They were generated before the program was
|
||||
// analyzed, and before ngtsc knew which symbols were actually NgModules. factoryMap contains
|
||||
// that knowledge now, so this transform filters the statement list and removes exported factories
|
||||
@ -221,7 +219,8 @@ function transformFactorySourceFile(
|
||||
// satisfy closure compiler.
|
||||
transformedStatements.push(nonEmptyExport);
|
||||
}
|
||||
file.statements = ts.createNodeArray(transformedStatements);
|
||||
|
||||
file = ts.updateSourceFileNode(file, transformedStatements);
|
||||
|
||||
// If any imports to @angular/core were detected and rewritten (which happens when compiling
|
||||
// @angular/core), go through the SourceFile and rewrite references to symbols imported from core.
|
||||
|
@ -42,8 +42,7 @@ function flipIvySwitchInFile(sf: ts.SourceFile): ts.SourceFile {
|
||||
|
||||
// Only update the statements in the SourceFile if any have changed.
|
||||
if (newStatements !== undefined) {
|
||||
sf = ts.getMutableClone(sf);
|
||||
sf.statements = ts.createNodeArray(newStatements);
|
||||
return ts.updateSourceFileNode(sf, newStatements);
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
@ -105,16 +104,12 @@ function flipIvySwitchesInVariableStatement(
|
||||
|
||||
// Find the post-switch variable identifier. If one can't be found, it's an error. This is
|
||||
// reported as a thrown error and not a diagnostic as transformers cannot output diagnostics.
|
||||
let newIdentifier = findPostSwitchIdentifier(statements, postSwitchName);
|
||||
const newIdentifier = findPostSwitchIdentifier(statements, postSwitchName);
|
||||
if (newIdentifier === null) {
|
||||
throw new Error(`Unable to find identifier ${postSwitchName} in ${
|
||||
stmt.getSourceFile().fileName} for the Ivy switch.`);
|
||||
}
|
||||
|
||||
// Copy the identifier with updateIdentifier(). This copies the internal information which
|
||||
// allows TS to write a correct reference to the identifier.
|
||||
newIdentifier = ts.updateIdentifier(newIdentifier);
|
||||
|
||||
newDeclarations[i] = ts.updateVariableDeclaration(
|
||||
/* node */ decl,
|
||||
/* name */ decl.name,
|
||||
|
@ -28,9 +28,7 @@ export function aliasTransformFactory(exportStatements: Map<string, Map<string,
|
||||
statements.push(stmt);
|
||||
});
|
||||
|
||||
file = ts.getMutableClone(file);
|
||||
file.statements = ts.createNodeArray(statements);
|
||||
return file;
|
||||
return ts.updateSourceFileNode(file, statements);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -239,32 +239,48 @@ export class ReturnTypeTransform implements DtsTransform {
|
||||
}
|
||||
|
||||
transformClassElement(element: ts.ClassElement, imports: ImportManager): ts.ClassElement {
|
||||
if (!ts.isMethodSignature(element)) {
|
||||
return element;
|
||||
// // TODO(alan-agius4): Remove when we no longer support TS 3.9
|
||||
// TS <= 3.9
|
||||
if (ts.isMethodSignature(element)) {
|
||||
const original = ts.getOriginalNode(element) as ts.MethodDeclaration;
|
||||
if (!this.typeReplacements.has(original)) {
|
||||
return element;
|
||||
}
|
||||
const returnType = this.typeReplacements.get(original)!;
|
||||
const tsReturnType = translateType(returnType, imports);
|
||||
const methodSignature = ts.updateMethodSignature(
|
||||
/* node */ element,
|
||||
/* typeParameters */ element.typeParameters,
|
||||
/* parameters */ element.parameters,
|
||||
/* type */ tsReturnType,
|
||||
/* name */ element.name,
|
||||
/* questionToken */ element.questionToken);
|
||||
|
||||
// Copy over any modifiers, these cannot be set during the `ts.updateMethodSignature` call.
|
||||
(methodSignature.modifiers as ts.ModifiersArray | undefined) = element.modifiers;
|
||||
|
||||
// A bug in the TypeScript declaration causes `ts.MethodSignature` not to be assignable to
|
||||
// `ts.ClassElement`. Since `element` was a `ts.MethodSignature` already, transforming it into
|
||||
// this type is actually correct.
|
||||
return methodSignature as unknown as ts.ClassElement;
|
||||
}
|
||||
|
||||
const original = ts.getOriginalNode(element) as ts.MethodDeclaration;
|
||||
if (!this.typeReplacements.has(original)) {
|
||||
return element;
|
||||
// TS 4.0 +
|
||||
if (ts.isMethodDeclaration(element)) {
|
||||
const original = ts.getOriginalNode(element, ts.isMethodDeclaration);
|
||||
if (!this.typeReplacements.has(original)) {
|
||||
return element;
|
||||
}
|
||||
const returnType = this.typeReplacements.get(original)!;
|
||||
const tsReturnType = translateType(returnType, imports);
|
||||
|
||||
return ts.updateMethod(
|
||||
element, element.decorators, element.modifiers, element.asteriskToken, element.name,
|
||||
element.questionToken, element.typeParameters, element.parameters, tsReturnType,
|
||||
element.body);
|
||||
}
|
||||
const returnType = this.typeReplacements.get(original)!;
|
||||
const tsReturnType = translateType(returnType, imports);
|
||||
|
||||
const methodSignature = ts.updateMethodSignature(
|
||||
/* node */ element,
|
||||
/* typeParameters */ element.typeParameters,
|
||||
/* parameters */ element.parameters,
|
||||
/* type */ tsReturnType,
|
||||
/* name */ element.name,
|
||||
/* questionToken */ element.questionToken);
|
||||
|
||||
// Copy over any modifiers, these cannot be set during the `ts.updateMethodSignature` call.
|
||||
methodSignature.modifiers = element.modifiers;
|
||||
|
||||
// A bug in the TypeScript declaration causes `ts.MethodSignature` not to be assignable to
|
||||
// `ts.ClassElement`. Since `element` was a `ts.MethodSignature` already, transforming it into
|
||||
// this type is actually correct.
|
||||
return methodSignature as unknown as ts.ClassElement;
|
||||
return element;
|
||||
}
|
||||
|
||||
transformFunctionDeclaration(element: ts.FunctionDeclaration, imports: ImportManager):
|
||||
|
@ -187,8 +187,8 @@ class IvyTransformationVisitor extends Visitor {
|
||||
|
||||
// Create a new `NodeArray` with the filtered decorators that sourcemaps back to the original.
|
||||
const array = ts.createNodeArray(filtered);
|
||||
array.pos = node.decorators.pos;
|
||||
array.end = node.decorators.end;
|
||||
(array.pos as number) = node.decorators.pos;
|
||||
(array.end as number) = node.decorators.end;
|
||||
return array;
|
||||
}
|
||||
|
||||
|
@ -40,8 +40,9 @@ export function addImports(
|
||||
// for @fileoverview Closure annotation. If there is no @fileoverview annotations, this
|
||||
// statement would be a noop.
|
||||
const fileoverviewAnchorStmt = ts.createNotEmittedStatement(sf);
|
||||
sf.statements = ts.createNodeArray(
|
||||
[fileoverviewAnchorStmt, ...existingImports, ...addedImports, ...extraStatements, ...body]);
|
||||
return ts.updateSourceFileNode(sf, ts.createNodeArray([
|
||||
fileoverviewAnchorStmt, ...existingImports, ...addedImports, ...extraStatements, ...body
|
||||
]));
|
||||
}
|
||||
|
||||
return sf;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
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 {LocalizedString} from '@angular/compiler/src/output/output_ast';
|
||||
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {DefaultImportRecorder, ImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER, NoopImportRewriter} from '../../imports';
|
||||
@ -24,6 +24,11 @@ export class Context {
|
||||
}
|
||||
}
|
||||
|
||||
const UNARY_OPERATORS = new Map<UnaryOperator, ts.PrefixUnaryOperator>([
|
||||
[UnaryOperator.Minus, ts.SyntaxKind.MinusToken],
|
||||
[UnaryOperator.Plus, ts.SyntaxKind.PlusToken],
|
||||
]);
|
||||
|
||||
const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
|
||||
[BinaryOperator.And, ts.SyntaxKind.AmpersandAmpersandToken],
|
||||
[BinaryOperator.Bigger, ts.SyntaxKind.GreaterThanToken],
|
||||
@ -361,6 +366,14 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
undefined, ts.createBlock(ast.statements.map(stmt => stmt.visitStatement(this, context))));
|
||||
}
|
||||
|
||||
visitUnaryOperatorExpr(ast: UnaryOperatorExpr, context: Context): ts.Expression {
|
||||
if (!UNARY_OPERATORS.has(ast.operator)) {
|
||||
throw new Error(`Unknown unary operator: ${UnaryOperator[ast.operator]}`);
|
||||
}
|
||||
return ts.createPrefix(
|
||||
UNARY_OPERATORS.get(ast.operator)!, ast.expr.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: Context): ts.Expression {
|
||||
if (!BINARY_OPERATORS.has(ast.operator)) {
|
||||
throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`);
|
||||
@ -513,7 +526,11 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
|
||||
visitLiteralExpr(ast: LiteralExpr, context: Context): ts.TypeNode {
|
||||
if (ast.value === null) {
|
||||
return ts.createKeywordTypeNode(ts.SyntaxKind.NullKeyword);
|
||||
// TODO(alan-agius4): Remove when we no longer support TS 3.9
|
||||
// Use: return ts.createLiteralTypeNode(ts.createNull()) directly.
|
||||
return ts.versionMajorMinor.charAt(0) === '4' ?
|
||||
ts.createLiteralTypeNode(ts.createNull() as any) :
|
||||
ts.createKeywordTypeNode(ts.SyntaxKind.NullKeyword as any);
|
||||
} else if (ast.value === undefined) {
|
||||
return ts.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword);
|
||||
} else if (typeof ast.value === 'boolean') {
|
||||
@ -567,6 +584,10 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitUnaryOperatorExpr(ast: UnaryOperatorExpr, context: Context) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: Context) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@ -679,7 +700,7 @@ function createLocalizedStringTaggedTemplate(
|
||||
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
||||
function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
||||
const node: ts.TemplateLiteralLikeNode = ts.createTemplateHead(cooked, raw);
|
||||
node.kind = ts.SyntaxKind.TemplateMiddle;
|
||||
(node.kind as ts.SyntaxKind) = ts.SyntaxKind.TemplateMiddle;
|
||||
return node as ts.TemplateMiddle;
|
||||
}
|
||||
|
||||
@ -687,7 +708,7 @@ function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
||||
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
||||
function createTemplateTail(cooked: string, raw: string): ts.TemplateTail {
|
||||
const node: ts.TemplateLiteralLikeNode = ts.createTemplateHead(cooked, raw);
|
||||
node.kind = ts.SyntaxKind.TemplateTail;
|
||||
(node.kind as ts.SyntaxKind) = ts.SyntaxKind.TemplateTail;
|
||||
return node as ts.TemplateTail;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,14 @@ export interface TemplateTypeChecker {
|
||||
*/
|
||||
resetOverrides(): void;
|
||||
|
||||
/**
|
||||
* Retrieve the template in use for the given component.
|
||||
*
|
||||
* If the template has been overridden via `overrideComponentTemplate`, this will retrieve the
|
||||
* overridden template nodes.
|
||||
*/
|
||||
getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null;
|
||||
|
||||
/**
|
||||
* Provide a new template string that will be used in place of the user-defined template when
|
||||
* checking or operating on the given component.
|
||||
|
@ -48,6 +48,29 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
||||
}
|
||||
}
|
||||
|
||||
getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null {
|
||||
this.ensureShimForComponent(component);
|
||||
|
||||
const sf = component.getSourceFile();
|
||||
const sfPath = absoluteFromSourceFile(sf);
|
||||
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
||||
|
||||
const fileRecord = this.getFileData(sfPath);
|
||||
|
||||
if (!fileRecord.shimData.has(shimPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const templateId = fileRecord.sourceManager.getTemplateId(component);
|
||||
const shimRecord = fileRecord.shimData.get(shimPath)!;
|
||||
|
||||
if (!shimRecord.templates.has(templateId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return shimRecord.templates.get(templateId)!.template;
|
||||
}
|
||||
|
||||
overrideComponentTemplate(component: ts.ClassDeclaration, template: string):
|
||||
{nodes: TmplAstNode[], errors?: ParseError[]} {
|
||||
const {nodes, errors} = parseTemplate(template, 'override.html', {
|
||||
|
@ -6,14 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ParseSourceFile, R3TargetBinder, SchemaMetadata, TmplAstNode} from '@angular/compiler';
|
||||
import {BoundTarget, ParseSourceFile, R3TargetBinder, SchemaMetadata, TmplAstNode} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
import {ImportManager} from '../../translator';
|
||||
import {ComponentToShimMappingStrategy, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
||||
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
||||
|
||||
import {TemplateDiagnostic} from './diagnostics';
|
||||
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
|
||||
@ -41,6 +41,28 @@ export interface ShimTypeCheckingData {
|
||||
* Whether any inline operations for the input file were required to generate this shim.
|
||||
*/
|
||||
hasInlines: boolean;
|
||||
|
||||
/**
|
||||
* Map of `TemplateId` to information collected about the template during the template
|
||||
* type-checking process.
|
||||
*/
|
||||
templates: Map<TemplateId, TemplateData>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data tracked for each template processed by the template type-checking system.
|
||||
*/
|
||||
export interface TemplateData {
|
||||
/**
|
||||
* Template nodes for which the TCB was generated.
|
||||
*/
|
||||
template: TmplAstNode[];
|
||||
|
||||
/**
|
||||
* `BoundTarget` which was used to generate the TCB, and contains bindings for the associated
|
||||
* template nodes.
|
||||
*/
|
||||
boundTarget: BoundTarget<TypeCheckableDirectiveMeta>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,6 +101,12 @@ export interface PendingShimData {
|
||||
* Shim file in the process of being generated.
|
||||
*/
|
||||
file: TypeCheckFile;
|
||||
|
||||
|
||||
/**
|
||||
* Map of `TemplateId` to information collected about the template as it's ingested.
|
||||
*/
|
||||
templates: Map<TemplateId, TemplateData>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,6 +223,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||
const fileData = this.dataForFile(ref.node.getSourceFile());
|
||||
const shimData = this.pendingShimForComponent(ref.node);
|
||||
const boundTarget = binder.bind({template});
|
||||
|
||||
// Get all of the directives used in the template and record type constructors for all of them.
|
||||
for (const dir of boundTarget.getUsedDirectives()) {
|
||||
const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;
|
||||
@ -221,6 +250,11 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||
});
|
||||
}
|
||||
}
|
||||
const templateId = fileData.sourceManager.getTemplateId(ref.node);
|
||||
shimData.templates.set(templateId, {
|
||||
template,
|
||||
boundTarget,
|
||||
});
|
||||
|
||||
const tcbRequiresInline = requiresInlineTypeCheckBlock(ref.node);
|
||||
|
||||
@ -231,7 +265,6 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||
// and inlining would be required.
|
||||
|
||||
// Record diagnostics to indicate the issues with this template.
|
||||
const templateId = fileData.sourceManager.getTemplateId(ref.node);
|
||||
if (tcbRequiresInline) {
|
||||
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
||||
}
|
||||
@ -348,6 +381,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||
],
|
||||
hasInlines: pendingFileData.hasInlines,
|
||||
path: pendingShimData.file.fileName,
|
||||
templates: pendingShimData.templates,
|
||||
});
|
||||
updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
|
||||
}
|
||||
@ -380,6 +414,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||
oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
|
||||
file: new TypeCheckFile(
|
||||
shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
|
||||
templates: new Map<TemplateId, TemplateData>(),
|
||||
});
|
||||
}
|
||||
return fileData.shimData.get(shimPath)!;
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '@angular/compiler';
|
||||
import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, Unary} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
import {TypeCheckingConfig} from '../api';
|
||||
|
||||
@ -17,7 +17,12 @@ export const NULL_AS_ANY =
|
||||
ts.createAsExpression(ts.createNull(), ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
||||
const UNDEFINED = ts.createIdentifier('undefined');
|
||||
|
||||
const BINARY_OPS = new Map<string, ts.SyntaxKind>([
|
||||
const UNARY_OPS = new Map<string, ts.PrefixUnaryOperator>([
|
||||
['+', ts.SyntaxKind.PlusToken],
|
||||
['-', ts.SyntaxKind.MinusToken],
|
||||
]);
|
||||
|
||||
const BINARY_OPS = new Map<string, ts.BinaryOperator>([
|
||||
['+', ts.SyntaxKind.PlusToken],
|
||||
['-', ts.SyntaxKind.MinusToken],
|
||||
['<', ts.SyntaxKind.LessThanToken],
|
||||
@ -74,6 +79,17 @@ class AstTranslator implements AstVisitor {
|
||||
return ast.visit(this);
|
||||
}
|
||||
|
||||
visitUnary(ast: Unary): ts.Expression {
|
||||
const expr = this.translate(ast.expr);
|
||||
const op = UNARY_OPS.get(ast.operator);
|
||||
if (op === undefined) {
|
||||
throw new Error(`Unsupported Unary.operator: ${ast.operator}`);
|
||||
}
|
||||
const node = wrapForDiagnostics(ts.createPrefix(op, expr));
|
||||
addParseSpanInfo(node, ast.sourceSpan);
|
||||
return node;
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary): ts.Expression {
|
||||
const lhs = wrapForDiagnostics(this.translate(ast.left));
|
||||
const rhs = wrapForDiagnostics(this.translate(ast.right));
|
||||
@ -81,7 +97,7 @@ class AstTranslator implements AstVisitor {
|
||||
if (op === undefined) {
|
||||
throw new Error(`Unsupported Binary.operation: ${ast.operation}`);
|
||||
}
|
||||
const node = ts.createBinary(lhs, op as any, rhs);
|
||||
const node = ts.createBinary(lhs, op, rhs);
|
||||
addParseSpanInfo(node, ast.sourceSpan);
|
||||
return node;
|
||||
}
|
||||
@ -314,6 +330,9 @@ class VeSafeLhsInferenceBugDetector implements AstVisitor {
|
||||
return ast.receiver.visit(VeSafeLhsInferenceBugDetector.SINGLETON);
|
||||
}
|
||||
|
||||
visitUnary(ast: Unary): boolean {
|
||||
return ast.expr.visit(this);
|
||||
}
|
||||
visitBinary(ast: Binary): boolean {
|
||||
return ast.left.visit(this) || ast.right.visit(this);
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ export function canEmitType(type: ts.TypeNode, resolver: TypeReferenceResolver):
|
||||
visitTypeReferenceNode: type => canEmitTypeReference(type),
|
||||
visitArrayTypeNode: type => canEmitTypeWorker(type.elementType),
|
||||
visitKeywordType: () => true,
|
||||
visitLiteralType: () => true,
|
||||
visitOtherType: () => false,
|
||||
});
|
||||
}
|
||||
@ -111,6 +112,7 @@ export class TypeEmitter {
|
||||
visitTypeReferenceNode: type => this.emitTypeReference(type),
|
||||
visitArrayTypeNode: type => ts.updateArrayTypeNode(type, this.emitType(type.elementType)),
|
||||
visitKeywordType: type => type,
|
||||
visitLiteralType: type => type,
|
||||
visitOtherType: () => {
|
||||
throw new Error('Unable to emit a complex type');
|
||||
},
|
||||
@ -159,6 +161,7 @@ interface TypeEmitterVisitor<R> {
|
||||
visitTypeReferenceNode(type: ts.TypeReferenceNode): R;
|
||||
visitArrayTypeNode(type: ts.ArrayTypeNode): R;
|
||||
visitKeywordType(type: ts.KeywordTypeNode): R;
|
||||
visitLiteralType(type: ts.LiteralTypeNode): R;
|
||||
visitOtherType(type: ts.TypeNode): R;
|
||||
}
|
||||
|
||||
@ -167,6 +170,8 @@ function visitTypeNode<R>(type: ts.TypeNode, visitor: TypeEmitterVisitor<R>): R
|
||||
return visitor.visitTypeReferenceNode(type);
|
||||
} else if (ts.isArrayTypeNode(type)) {
|
||||
return visitor.visitArrayTypeNode(type);
|
||||
} else if (ts.isLiteralTypeNode(type)) {
|
||||
return visitor.visitLiteralType(type);
|
||||
}
|
||||
|
||||
switch (type.kind) {
|
||||
|
@ -248,6 +248,17 @@ runInEachFileSystem(() => {
|
||||
expect(messages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should treat unary operators as literal types', () => {
|
||||
const messages = diagnose(`{{ test(-1) + test(+1) + test(-2) }}`, `
|
||||
class TestComponent {
|
||||
test(value: -1 | 1): number { return value; }
|
||||
}`);
|
||||
|
||||
expect(messages).toEqual([
|
||||
`TestComponent.html(1, 31): Argument of type '-2' is not assignable to parameter of type '1 | -1'.`,
|
||||
]);
|
||||
});
|
||||
|
||||
describe('outputs', () => {
|
||||
it('should produce a diagnostic for directive outputs', () => {
|
||||
const messages = diagnose(
|
||||
@ -445,7 +456,7 @@ class TestComponent {
|
||||
}`);
|
||||
|
||||
expect(messages).toEqual(
|
||||
[`TestComponent.html(1, 15): Type '2' is not assignable to type 'string'.`]);
|
||||
[`TestComponent.html(1, 15): Type 'number' is not assignable to type 'string'.`]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -10,6 +10,10 @@ import {tcb, TestDeclaration} from './test_utils';
|
||||
|
||||
describe('type check blocks diagnostics', () => {
|
||||
describe('parse spans', () => {
|
||||
it('should annotate unary ops', () => {
|
||||
expect(tcbWithSpans('{{ -a }}')).toContain('(-((ctx).a /*4,5*/) /*4,5*/) /*3,5*/');
|
||||
});
|
||||
|
||||
it('should annotate binary ops', () => {
|
||||
expect(tcbWithSpans('{{ a + b }}'))
|
||||
.toContain('(((ctx).a /*3,4*/) /*3,4*/) + (((ctx).b /*7,8*/) /*7,8*/) /*3,8*/');
|
||||
|
@ -31,6 +31,11 @@ describe('type check blocks', () => {
|
||||
expect(tcb(TEMPLATE)).toContain('((((ctx).a))!);');
|
||||
});
|
||||
|
||||
it('should handle unary - operator', () => {
|
||||
const TEMPLATE = `{{-1}}`;
|
||||
expect(tcb(TEMPLATE)).toContain('(-1);');
|
||||
});
|
||||
|
||||
it('should handle keyed property access', () => {
|
||||
const TEMPLATE = `{{a[b]}}`;
|
||||
expect(tcb(TEMPLATE)).toContain('(((ctx).a))[((ctx).b)];');
|
||||
|
@ -353,5 +353,40 @@ runInEachFileSystem(os => {
|
||||
expect(diags2[0].messageText).toContain('invalid-element-b');
|
||||
expect(diags2[0].messageText).not.toContain('invalid-element-a');
|
||||
});
|
||||
|
||||
describe('getTemplateOfComponent()', () => {
|
||||
it('should provide access to a component\'s real template', () => {
|
||||
const fileName = absoluteFrom('/main.ts');
|
||||
const {program, templateTypeChecker} = setup([{
|
||||
fileName,
|
||||
templates: {
|
||||
'Cmp': '<div>Template</div>',
|
||||
},
|
||||
}]);
|
||||
const cmp = getClass(getSourceFileOrError(program, fileName), 'Cmp');
|
||||
|
||||
const nodes = templateTypeChecker.getTemplate(cmp)!;
|
||||
expect(nodes).not.toBeNull();
|
||||
expect(nodes[0].sourceSpan.start.file.content).toBe('<div>Template</div>');
|
||||
});
|
||||
|
||||
it('should provide access to an overridden template', () => {
|
||||
const fileName = absoluteFrom('/main.ts');
|
||||
const {program, templateTypeChecker} = setup([{
|
||||
fileName,
|
||||
templates: {
|
||||
'Cmp': '<div>Template</div>',
|
||||
},
|
||||
}]);
|
||||
const cmp = getClass(getSourceFileOrError(program, fileName), 'Cmp');
|
||||
|
||||
templateTypeChecker.overrideComponentTemplate(cmp, '<div>Overridden</div>');
|
||||
templateTypeChecker.getDiagnosticsForComponent(cmp);
|
||||
|
||||
const nodes = templateTypeChecker.getTemplate(cmp)!;
|
||||
expect(nodes).not.toBeNull();
|
||||
expect(nodes[0].sourceSpan.start.file.content).toBe('<div>Overridden</div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -140,10 +140,16 @@ function createCtorParametersClassPropertyType(): ts.TypeNode {
|
||||
undefined),
|
||||
])),
|
||||
undefined));
|
||||
return ts.createFunctionTypeNode(
|
||||
undefined, [],
|
||||
ts.createArrayTypeNode(
|
||||
ts.createUnionTypeNode([ts.createTypeLiteralNode(typeElements), ts.createNull()])));
|
||||
|
||||
// TODO(alan-agius4): Remove when we no longer support TS 3.9
|
||||
const nullLiteral = ts.createNull() as any;
|
||||
const nullType = ts.versionMajorMinor.charAt(0) === '4' ?
|
||||
ts.createLiteralTypeNode(nullLiteral as any) :
|
||||
nullLiteral;
|
||||
return ts.createFunctionTypeNode(undefined, [], ts.createArrayTypeNode(ts.createUnionTypeNode([
|
||||
ts.createTypeLiteralNode(typeElements),
|
||||
nullType,
|
||||
])));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,8 +293,13 @@ function typeReferenceToExpression(
|
||||
// Ignore any generic types, just return the base type.
|
||||
return entityNameToExpression(typeRef.typeName);
|
||||
case ts.SyntaxKind.UnionType:
|
||||
// TODO(alan-agius4): remove `t.kind !== ts.SyntaxKind.NullKeyword` when
|
||||
// TS 3.9 support is dropped. In TS 4.0 NullKeyword is a child of LiteralType.
|
||||
const childTypeNodes =
|
||||
(node as ts.UnionTypeNode).types.filter(t => t.kind !== ts.SyntaxKind.NullKeyword);
|
||||
(node as ts.UnionTypeNode)
|
||||
.types.filter(
|
||||
t => t.kind !== ts.SyntaxKind.NullKeyword &&
|
||||
!(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
|
||||
return childTypeNodes.length === 1 ?
|
||||
typeReferenceToExpression(entityNameToExpression, childTypeNodes[0]) :
|
||||
undefined;
|
||||
@ -298,15 +309,20 @@ function typeReferenceToExpression(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given symbol refers to a value (as distinct from a type).
|
||||
* Checks whether a given symbol refers to a value that exists at runtime (as distinct from a type).
|
||||
*
|
||||
* Expands aliases, which is important for the case where
|
||||
* import * as x from 'some-module';
|
||||
* and x is now a value (the module object).
|
||||
*/
|
||||
function symbolIsValue(tc: ts.TypeChecker, sym: ts.Symbol): boolean {
|
||||
if (sym.flags & ts.SymbolFlags.Alias) sym = tc.getAliasedSymbol(sym);
|
||||
return (sym.flags & ts.SymbolFlags.Value) !== 0;
|
||||
function symbolIsRuntimeValue(typeChecker: ts.TypeChecker, symbol: ts.Symbol): boolean {
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
symbol = typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
|
||||
// Note that const enums are a special case, because
|
||||
// while they have a value, they don't exist at runtime.
|
||||
return (symbol.flags & ts.SymbolFlags.Value & ts.SymbolFlags.ConstEnumExcludes) !== 0;
|
||||
}
|
||||
|
||||
/** ParameterDecorationInfo describes the information for a single constructor parameter. */
|
||||
@ -351,7 +367,7 @@ export function getDownlevelDecoratorsTransform(
|
||||
const symbol = typeChecker.getSymbolAtLocation(name);
|
||||
// Check if the entity name references a symbol that is an actual value. If it is not, it
|
||||
// cannot be referenced by an expression, so return undefined.
|
||||
if (!symbol || !symbolIsValue(typeChecker, symbol) || !symbol.declarations ||
|
||||
if (!symbol || !symbolIsRuntimeValue(typeChecker, symbol) || !symbol.declarations ||
|
||||
symbol.declarations.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
@ -429,7 +445,7 @@ export function getDownlevelDecoratorsTransform(
|
||||
|
||||
const name = (element.name as ts.Identifier).text;
|
||||
const mutable = ts.getMutableClone(element);
|
||||
mutable.decorators = decoratorsToKeep.length ?
|
||||
(mutable as any).decorators = decoratorsToKeep.length ?
|
||||
ts.setTextRange(ts.createNodeArray(decoratorsToKeep), mutable.decorators) :
|
||||
undefined;
|
||||
return [name, mutable, toLower];
|
||||
@ -546,8 +562,6 @@ export function getDownlevelDecoratorsTransform(
|
||||
}
|
||||
}
|
||||
|
||||
const newClassDeclaration = ts.getMutableClone(classDecl);
|
||||
|
||||
if (decoratorsToLower.length) {
|
||||
newMembers.push(createDecoratorClassProperty(decoratorsToLower));
|
||||
}
|
||||
@ -562,12 +576,13 @@ export function getDownlevelDecoratorsTransform(
|
||||
if (decoratedProperties.size) {
|
||||
newMembers.push(createPropDecoratorsClassProperty(diagnostics, decoratedProperties));
|
||||
}
|
||||
newClassDeclaration.members = ts.setTextRange(
|
||||
ts.createNodeArray(newMembers, newClassDeclaration.members.hasTrailingComma),
|
||||
classDecl.members);
|
||||
newClassDeclaration.decorators =
|
||||
decoratorsToKeep.length ? ts.createNodeArray(decoratorsToKeep) : undefined;
|
||||
return newClassDeclaration;
|
||||
|
||||
const members = ts.setTextRange(
|
||||
ts.createNodeArray(newMembers, classDecl.members.hasTrailingComma), classDecl.members);
|
||||
|
||||
return ts.updateClassDeclaration(
|
||||
classDecl, decoratorsToKeep.length ? decoratorsToKeep : undefined, classDecl.modifiers,
|
||||
classDecl.name, classDecl.typeParameters, classDecl.heritageClauses, members);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,14 +167,13 @@ function transformSourceFile(
|
||||
|
||||
newStatements = tmpStatements;
|
||||
}
|
||||
// Note: We cannot use ts.updateSourcefile here as
|
||||
// it does not work well with decorators.
|
||||
// See https://github.com/Microsoft/TypeScript/issues/17384
|
||||
const newSf = ts.getMutableClone(sourceFile);
|
||||
|
||||
const newSf = ts.updateSourceFileNode(
|
||||
sourceFile, ts.setTextRange(ts.createNodeArray(newStatements), sourceFile.statements));
|
||||
if (!(sourceFile.flags & ts.NodeFlags.Synthesized)) {
|
||||
newSf.flags &= ~ts.NodeFlags.Synthesized;
|
||||
(newSf.flags as ts.NodeFlags) &= ~ts.NodeFlags.Synthesized;
|
||||
}
|
||||
newSf.statements = ts.setTextRange(ts.createNodeArray(newStatements), sourceFile.statements);
|
||||
|
||||
return newSf;
|
||||
}
|
||||
|
||||
@ -209,11 +208,6 @@ export interface RequestsMap {
|
||||
getRequests(sourceFile: ts.SourceFile): RequestLocationMap;
|
||||
}
|
||||
|
||||
interface MetadataAndLoweringRequests {
|
||||
metadata: ModuleMetadata|undefined;
|
||||
requests: RequestLocationMap;
|
||||
}
|
||||
|
||||
function isEligibleForLowering(node: ts.Node|undefined): boolean {
|
||||
if (node) {
|
||||
switch (node.kind) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, TypeofExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {LocalizedString} from '@angular/compiler/src/output/output_ast';
|
||||
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {error} from './util';
|
||||
@ -622,6 +622,23 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
||||
/* type */ undefined, this._visitStatements(expr.statements)));
|
||||
}
|
||||
|
||||
visitUnaryOperatorExpr(expr: UnaryOperatorExpr):
|
||||
RecordedNode<ts.UnaryExpression|ts.ParenthesizedExpression> {
|
||||
let unaryOperator: ts.BinaryOperator;
|
||||
switch (expr.operator) {
|
||||
case UnaryOperator.Minus:
|
||||
unaryOperator = ts.SyntaxKind.MinusToken;
|
||||
break;
|
||||
case UnaryOperator.Plus:
|
||||
unaryOperator = ts.SyntaxKind.PlusToken;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown operator: ${expr.operator}`);
|
||||
}
|
||||
const binary = ts.createPrefix(unaryOperator, expr.expr.visitExpression(this, null));
|
||||
return this.record(expr, expr.parens ? ts.createParen(binary) : binary);
|
||||
}
|
||||
|
||||
visitBinaryOperatorExpr(expr: BinaryOperatorExpr):
|
||||
RecordedNode<ts.BinaryExpression|ts.ParenthesizedExpression> {
|
||||
let binaryOperator: ts.BinaryOperator;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user