Skip to content
This repository was archived by the owner on Jul 1, 2024. It is now read-only.

Commit cdc8eec

Browse files
committed
Put explanations next to suggestions
1 parent d467d32 commit cdc8eec

File tree

4 files changed

+44
-50
lines changed

4 files changed

+44
-50
lines changed

src/comments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,5 @@ I'll bump it to the DT maintainer queue. Thank you for your patience, @${author}
125125

126126
// Introduction to the review when config files diverge from the
127127
// expected form
128-
export const suggestions = (user: string) =>
128+
export const explanations = (user: string) =>
129129
`@${user} I noticed these differences from the expected form. If you can revise your changes to avoid them, so much the better! Otherwise please reply with explanations why they're needed and a maintainer will take a look. Thanks!`;

src/compute-pr-actions.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Comments from "./comments";
22
import * as urls from "./urls";
33
import { PrInfo, BotResult, FileInfo } from "./pr-info";
4-
import { ReviewInfo, Suggestion } from "./pr-info";
4+
import { ReviewInfo, Explanation } from "./pr-info";
55
import { noNullish, flatten, unique, sameUser, min, sha256, abbrOid } from "./util/util";
66
import * as dayjs from "dayjs";
77
import * as advancedFormat from "dayjs/plugin/advancedFormat";
@@ -52,7 +52,7 @@ export interface Actions {
5252
targetColumn?: ColumnName;
5353
labels: LabelName[];
5454
responseComments: Comments.Comment[];
55-
suggestions: ({ path: string } & Suggestion)[];
55+
explanations: ({ path: string } & Explanation)[];
5656
shouldClose: boolean;
5757
shouldMerge: boolean;
5858
shouldUpdateLabels: boolean;
@@ -65,7 +65,7 @@ function createDefaultActions(): Actions {
6565
targetColumn: "Other",
6666
labels: [],
6767
responseComments: [],
68-
suggestions: [],
68+
explanations: [],
6969
shouldClose: false,
7070
shouldMerge: false,
7171
shouldUpdateLabels: true,
@@ -78,7 +78,7 @@ function createEmptyActions(): Actions {
7878
return {
7979
labels: [],
8080
responseComments: [],
81-
suggestions: [],
81+
explanations: [],
8282
shouldClose: false,
8383
shouldMerge: false,
8484
shouldUpdateLabels: false,
@@ -294,9 +294,9 @@ export function process(prInfo: BotResult,
294294
// Update intro comment
295295
post({ tag: "welcome", status: createWelcomeComment(info, post) });
296296

297-
// Propagate suggestions into actions
298-
context.suggestions = noNullish(flatten(info.pkgInfo.map(pkg => pkg.files.map(({ path, suggestion }) =>
299-
suggestion && { path, ...suggestion }))));
297+
// Propagate explanations into actions
298+
context.explanations = noNullish(flatten(info.pkgInfo.map(pkg => pkg.files.map(({ path, suspect }) =>
299+
suspect && { path, ...suspect }))));
300300

301301
// Ping reviewers when needed
302302
const headCommitAbbrOid = abbrOid(info.headCommitOid);
@@ -433,9 +433,6 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
433433

434434
const announceList = (what: string, xs: readonly string[]) => `${xs.length} ${what}${xs.length !== 1 ? "s" : ""}`;
435435
const usersToString = (users: string[]) => users.map(u => (info.isAuthor(u) ? "✎" : "") + "@" + u).join(", ");
436-
const reviewLink = (f: FileInfo) =>
437-
`[\`${f.path.replace(/^types\/(.*\/)/, "$1")}\`](${
438-
urls.review(info.pr_number)}/${info.headCommitOid}#diff-${sha256(f.path)})`;
439436

440437
display(``,
441438
`## ${announceList("package", info.packages)} in this PR`,
@@ -465,15 +462,6 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
465462
displayOwners("added", p.addedOwners);
466463
displayOwners("removed", p.deletedOwners);
467464
if (!info.authorIsOwner && p.owners.length >= 4 && p.addedOwners.some(info.isAuthor)) addedSelfToManyOwners++;
468-
469-
let showSuspects = false;
470-
for (const file of p.files) {
471-
if (!file.suspect) continue;
472-
if (!showSuspects) display(` - Config files to check:`);
473-
display(` - ${reviewLink(file)}: ${file.suspect}`);
474-
showSuspects = true;
475-
}
476-
477465
}
478466
if (addedSelfToManyOwners > 0) {
479467
display(``,
@@ -512,6 +500,9 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
512500
display(` * ${emoji(info.ciResult === "pass")} Continuous integration tests have ${expectedResults}`);
513501

514502
const approved = emoji(info.approved);
503+
const reviewLink = (f: FileInfo) =>
504+
`[\`${f.path.replace(/^types\/(.*\/)/, "$1")}\`](${
505+
urls.review(info.pr_number)}/${info.headCommitOid}#diff-${sha256(f.path)})`;
515506

516507
if (info.hasNewPackages) {
517508
display(` * ${approved} Only ${requiredApprover} can approve changes when there are new packages added`);
@@ -532,7 +523,9 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
532523
} else if (info.noOtherOwners) {
533524
display(` * ${approved} ${RequiredApprover} can merge changes when there are no other reviewers`);
534525
} else if (info.checkConfig) {
535-
display(` * ${approved} ${RequiredApprover} needs to approve changes which affect module config files`);
526+
const configFiles = flatten(info.pkgInfo.map(pkg => pkg.files.filter(({ kind }) => kind === "package-meta")));
527+
const links = configFiles.map(reviewLink);
528+
display(` * ${approved} ${RequiredApprover} needs to approve changes which affect module config files (${links.join(", ")})`);
536529
} else {
537530
display(` * ${approved} Only ${requiredApprover} can approve changes [without tests](${testsLink})`);
538531
}

src/execute-pr-actions.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export async function executePrActions(actions: Actions, pr: PR_repository_pullR
1818
...await getMutationsForProjectChanges(actions, pr),
1919
...getMutationsForComments(actions, pr.id, botComments),
2020
...getMutationsForCommentRemovals(actions, botComments),
21-
...getMutationsForSuggestions(actions, pr),
21+
...getMutationsForExplanations(actions, pr),
2222
...getMutationsForChangingPRState(actions, pr),
2323
]);
2424
if (!dry) {
@@ -102,23 +102,28 @@ function getMutationsForCommentRemovals(actions: Actions, botComments: ParsedCom
102102
});
103103
}
104104

105-
function getMutationsForSuggestions(actions: Actions, pr: PR_repository_pullRequest) {
106-
// Suggestions will be empty if we already reviewed this head
107-
if (actions.suggestions.length === 0) return [];
105+
function getMutationsForExplanations(actions: Actions, pr: PR_repository_pullRequest) {
106+
// Explanations will be empty if we already reviewed this head
107+
if (actions.explanations.length === 0) return [];
108108
if (!pr.author) throw new Error("Internal Error: expected to be checked");
109109
return [
110-
...actions.suggestions.map(({ path, startLine, endLine, text }) =>
111-
createMutation<schema.AddPullRequestReviewThreadInput>("addPullRequestReviewThread", {
110+
...actions.explanations.map(({ path, startLine, endLine, body }) => endLine
111+
? createMutation<schema.AddPullRequestReviewThreadInput>("addPullRequestReviewThread", {
112112
pullRequestId: pr.id,
113113
path,
114114
startLine: startLine === endLine ? undefined : startLine,
115115
line: endLine,
116-
body: "```suggestion\n" + text + "```",
116+
body,
117+
})
118+
: createMutation<schema.AddPullRequestReviewCommentInput>("addPullRequestReviewComment", {
119+
pullRequestId: pr.id,
120+
path,
121+
body,
117122
})
118123
),
119124
createMutation<schema.SubmitPullRequestReviewInput>("submitPullRequestReview", {
120125
pullRequestId: pr.id,
121-
body: comments.suggestions(pr.author.login),
126+
body: comments.explanations(pr.author.login),
122127
event: "COMMENT",
123128
}),
124129
];

src/pr-info.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,13 @@ type FileKind = "test" | "definition" | "markdown" | "package-meta" | "package-m
5858
export type FileInfo = {
5959
path: string,
6060
kind: FileKind,
61-
suspect?: string, // reason for a file being "package-meta" rather than "package-meta-ok"
62-
suggestion?: Suggestion, // The differences from the expected form, as GitHub suggestions
61+
suspect?: Explanation // reason for a file being "package-meta" rather than "package-meta-ok"
6362
};
6463

65-
export interface Suggestion {
66-
readonly startLine: number;
67-
readonly endLine: number;
68-
readonly text: string;
64+
export interface Explanation {
65+
readonly startLine?: number;
66+
readonly endLine?: number;
67+
readonly body: string;
6968
}
7069

7170
export type ReviewInfo = {
@@ -188,7 +187,7 @@ export async function queryPRInfo(prNumber: number) {
188187
interface Refs {
189188
readonly head: string;
190189
readonly master: "master";
191-
readonly latestSuggestions: string;
190+
readonly latestExplanations: string;
192191
}
193192

194193
// The GQL response => Useful data for us
@@ -220,8 +219,8 @@ export async function deriveStateForPR(
220219
const refs = {
221220
head: prInfo.headRefOid,
222221
master: "master",
223-
// Exclude existing suggestions from subsequent reviews
224-
latestSuggestions: max(noNullish(prInfo.reviews?.nodes).filter(review => !authorNotBot(review)), (a, b) =>
222+
// Exclude existing explanations from subsequent reviews
223+
latestExplanations: max(noNullish(prInfo.reviews?.nodes).filter(review => !authorNotBot(review)), (a, b) =>
225224
Date.parse(a.submittedAt) - Date.parse(b.submittedAt))?.commit?.oid,
226225
} as const;
227226
const pkgInfoEtc = await getPackageInfosEtc(
@@ -355,19 +354,19 @@ async function categorizeFile(path: string, getContents: GetContents): Promise<[
355354
case "md": return [pkg, { path, kind: "markdown" }];
356355
default: {
357356
const suspect = await configSuspicious(path, getContents);
358-
return [pkg, { path, kind: suspect ? "package-meta" : "package-meta-ok", ...suspect }];
357+
return [pkg, { path, kind: suspect ? "package-meta" : "package-meta-ok", suspect }];
359358
}
360359
}
361360
}
362361

363362
interface ConfigSuspicious {
364-
(path: string, getContents: GetContents): Promise<{ suspect: string, sugestion?: Suggestion } | undefined>;
365-
[basename: string]: (newText: string, getContents: GetContents) => Promise<{ suspect: string, suggestion?: Suggestion } | undefined>;
363+
(path: string, getContents: GetContents): Promise<Explanation | undefined>;
364+
[basename: string]: (newText: string, getContents: GetContents) => Promise<Explanation | undefined>;
366365
}
367366
const configSuspicious = <ConfigSuspicious>(async (path, getContents) => {
368367
const basename = path.replace(/.*\//, "");
369368
const checker = configSuspicious[basename];
370-
if (!checker) return { suspect: `edited` };
369+
if (!checker) return { body: `edited` };
371370
const newText = await getContents("head");
372371
// Removing tslint.json, tsconfig.json, package.json and
373372
// OTHER_FILES.txt is checked by the CI. Specifics are in my commit
@@ -426,7 +425,7 @@ function makeChecker(expectedForm: any, expectedFormUrl: string, options?: { par
426425
if (options && "parse" in options) {
427426
suggestion = options.parse(newText);
428427
} else {
429-
try { suggestion = JSON.parse(newText); } catch (e) { if (e instanceof SyntaxError) return { suspect: `couldn't parse json: ${e.message}` }; }
428+
try { suggestion = JSON.parse(newText); } catch (e) { if (e instanceof SyntaxError) return { body: `couldn't parse json: ${e.message}` }; }
430429
}
431430
const newData = jsonDiff.deepClone(suggestion);
432431
if (options && "ignore" in options) options.ignore(newData);
@@ -435,15 +434,12 @@ function makeChecker(expectedForm: any, expectedFormUrl: string, options?: { par
435434
// suspect
436435
const vsMaster = await ignoreExistingDiffs("master");
437436
if (!vsMaster) return undefined;
438-
if (vsMaster.done) return { suspect: vsMaster.suspect };
437+
if (vsMaster.done) return { body: vsMaster.suspect };
439438
// whereas getting closer relative to existing suggestions means
440439
// no new suggestions
441-
if (!await ignoreExistingDiffs("latestSuggestions")) return { suspect: vsMaster.suspect };
440+
if (!await ignoreExistingDiffs("latestExplanations")) return { body: vsMaster.suspect };
442441
jsonDiff.applyPatch(suggestion, jsonDiff.compare(newData, towardsIt));
443-
return {
444-
suspect: vsMaster.suspect,
445-
suggestion: makeSuggestion(),
446-
};
442+
return makeSuggestion();
447443

448444
// Apply any preexisting diffs to towardsIt
449445
async function ignoreExistingDiffs(ref: keyof Refs) {
@@ -493,7 +489,7 @@ function makeChecker(expectedForm: any, expectedFormUrl: string, options?: { par
493489
return {
494490
startLine,
495491
endLine,
496-
text: suggestionLines.join(""),
492+
body: vsMaster!.suspect + "\n```suggestion\n" + suggestionLines.join("") + "```",
497493
};
498494
}
499495
};

0 commit comments

Comments
 (0)