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

Commit 0a29b2f

Browse files
committed
Put explanations next to suggestions
1 parent 4fea6f7 commit 0a29b2f

File tree

4 files changed

+39
-50
lines changed

4 files changed

+39
-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
// required form
128-
export const suggestions = (user: string) =>
128+
export const explanations = (user: string) =>
129129
`@${user} I noticed these differences from the required 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
@@ -2,7 +2,7 @@ import * as Comments from "./comments";
22
import * as urls from "./urls";
33
import { PrInfo, BotNotFail, FileInfo } from "./pr-info";
44
import { CIResult } from "./util/CIResult";
5-
import { ReviewInfo, Suggestion } from "./pr-info";
5+
import { ReviewInfo, Explanation } from "./pr-info";
66
import { noNullish, flatten, unique, sameUser, daysSince, sha256, min } from "./util/util";
77

88
type ColumnName =
@@ -51,7 +51,7 @@ export interface Actions {
5151
targetColumn?: ColumnName;
5252
labels: LabelName[];
5353
responseComments: Comments.Comment[];
54-
suggestions: ({ path: string } & Suggestion)[];
54+
explanations: ({ path: string } & Explanation)[];
5555
shouldClose: boolean;
5656
shouldMerge: boolean;
5757
shouldUpdateLabels: boolean;
@@ -65,7 +65,7 @@ function createDefaultActions(pr_number: number): Actions {
6565
targetColumn: "Other",
6666
labels: [],
6767
responseComments: [],
68-
suggestions: [],
68+
explanations: [],
6969
shouldClose: false,
7070
shouldMerge: false,
7171
shouldUpdateLabels: true,
@@ -79,7 +79,7 @@ function createEmptyActions(prNumber: number): Actions {
7979
pr_number: prNumber,
8080
labels: [],
8181
responseComments: [],
82-
suggestions: [],
82+
explanations: [],
8383
shouldClose: false,
8484
shouldMerge: false,
8585
shouldUpdateLabels: false,
@@ -302,9 +302,9 @@ export function process(prInfo: BotNotFail,
302302
// Update intro comment
303303
post({ tag: "welcome", status: createWelcomeComment(info, post) });
304304

305-
// Propagate suggestions into actions
306-
context.suggestions = noNullish(flatten(info.pkgInfo.map(pkg => pkg.files.map(({ path, suggestion }) =>
307-
suggestion && { path, ...suggestion }))));
305+
// Propagate explanations into actions
306+
context.explanations = noNullish(flatten(info.pkgInfo.map(pkg => pkg.files.map(({ path, suspect }) =>
307+
suspect && { path, ...suspect }))));
308308

309309
// Ping reviewers when needed
310310
if (!(info.hasChangereqs || info.approvedBy.includes("owner") || info.approvedBy.includes("maintainer"))) {
@@ -440,9 +440,6 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
440440

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

447444
display(``,
448445
`## ${announceList("package", info.packages)} in this PR`,
@@ -473,15 +470,6 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
473470
displayOwners("added", p.addedOwners);
474471
displayOwners("removed", p.deletedOwners);
475472
if (!info.authorIsOwner && p.owners.length >= 4 && p.addedOwners.some(info.isAuthor)) addedSelfToManyOwners++;
476-
477-
let showSuspects = false;
478-
for (const file of p.files) {
479-
if (!file.suspect) continue;
480-
if (!showSuspects) display(` - Config files to check:`);
481-
display(` - ${reviewLink(file)}: ${file.suspect}`);
482-
showSuspects = true;
483-
}
484-
485473
}
486474
if (addedSelfToManyOwners > 0) {
487475
display(``,
@@ -520,6 +508,9 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
520508
display(` * ${emoji(info.ciResult === CIResult.Pass)} Continuous integration tests have ${expectedResults}`);
521509

522510
const approved = emoji(info.approved);
511+
const reviewLink = (f: FileInfo) =>
512+
`[\`${f.path.replace(/^types\/(.*\/)/, "$1")}\`](${
513+
urls.review(info.pr_number)}/${info.headCommitOid}#diff-${sha256(f.path)})`;
523514

524515
if (info.hasNewPackages) {
525516
display(` * ${approved} Only ${requiredApprover} can approve changes when there are new packages added`);
@@ -540,7 +531,9 @@ function createWelcomeComment(info: ExtendedPrInfo, post: (c: Comments.Comment)
540531
} else if (info.noOtherOwners) {
541532
display(` * ${approved} ${RequiredApprover} can merge changes when there are no other reviewers`);
542533
} else if (info.checkConfig) {
543-
display(` * ${approved} ${RequiredApprover} needs to approve changes which affect module config files`);
534+
const configFiles = flatten(info.pkgInfo.map(pkg => pkg.files.filter(({ kind }) => kind === "package-meta")));
535+
const links = configFiles.map(reviewLink);
536+
display(` * ${approved} ${RequiredApprover} needs to approve changes which affect module config files (${links.join(", ")})`);
544537
} else {
545538
display(` * ${approved} Only ${requiredApprover} can approve changes [without tests](${testsLink})`);
546539
}

src/execute-pr-actions.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export async function executePrActions(actions: Actions, info: PRQueryResult, dr
3131
...await getMutationsForProjectChanges(actions, pr),
3232
...getMutationsForComments(actions, pr.id, botComments),
3333
...getMutationsForCommentRemovals(actions, botComments),
34-
...getMutationsForSuggestions(actions, pr),
34+
...getMutationsForExplanations(actions, pr),
3535
...getMutationsForChangingPRState(actions, pr),
3636
]);
3737
if (!dry) {
@@ -113,26 +113,26 @@ function getMutationsForCommentRemovals(actions: Actions, botComments: ParsedCom
113113
});
114114
}
115115

116-
function getMutationsForSuggestions(actions: Actions, pr: PR_repository_pullRequest) {
117-
// Suggestions will be empty if we already reviewed this head
118-
if (actions.suggestions.length === 0) return [];
116+
function getMutationsForExplanations(actions: Actions, pr: PR_repository_pullRequest) {
117+
// Explanations will be empty if we already reviewed this head
118+
if (actions.explanations.length === 0) return [];
119119
if (!pr.author) throw new Error("Internal Error: expected to be checked");
120120
return [
121-
...actions.suggestions.map(({ path, startLine, endLine, text }) =>
121+
...actions.explanations.map(({ path, startLine, endLine, body }) =>
122122
createMutation(addPullRequestReviewThread, {
123123
input: {
124124
pullRequestId: pr.id,
125125
path,
126126
startLine: startLine === endLine ? undefined : startLine,
127127
line: endLine,
128-
body: "```suggestion\n" + text + "```",
128+
body,
129129
}
130130
})
131131
),
132132
createMutation(submitPullRequestReview, {
133133
input: {
134134
pullRequestId: pr.id,
135-
body: comments.suggestions(pr.author.login),
135+
body: comments.explanations(pr.author.login),
136136
event: "COMMENT",
137137
}
138138
}),

src/pr-info.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,13 @@ type FileKind = "test" | "definition" | "markdown" | "package-meta" | "package-m
7070
export type FileInfo = {
7171
path: string,
7272
kind: FileKind,
73-
suspect?: string, // reason for a file being "package-meta" rather than "package-meta-ok"
74-
suggestion?: Suggestion, // The differences from the required form, as GitHub suggestions
73+
suspect?: Explanation // reason for a file being "package-meta" rather than "package-meta-ok"
7574
};
7675

77-
export interface Suggestion {
78-
readonly startLine: number;
79-
readonly endLine: number;
80-
readonly text: string;
76+
export interface Explanation {
77+
readonly startLine?: number;
78+
readonly endLine?: number;
79+
readonly body: string;
8180
}
8281

8382
export type ReviewInfo = {
@@ -206,7 +205,7 @@ export async function queryPRInfo(prNumber: number) {
206205
interface Refs {
207206
readonly head: string;
208207
readonly master: "master";
209-
readonly latestSuggestions: string;
208+
readonly latestExplanations: string;
210209
}
211210

212211
// The GQL response => Useful data for us
@@ -241,8 +240,8 @@ export async function deriveStateForPR(
241240
const refs = {
242241
head: headCommit.oid,
243242
master: "master",
244-
// Exclude existing suggestions from subsequent reviews
245-
latestSuggestions: max(noNullish(prInfo.reviews?.nodes).filter(review => !authorNotBot(review)), (a, b) =>
243+
// Exclude existing explanations from subsequent reviews
244+
latestExplanations: max(noNullish(prInfo.reviews?.nodes).filter(review => !authorNotBot(review)), (a, b) =>
246245
Date.parse(a.submittedAt) - Date.parse(b.submittedAt))?.commit?.oid,
247246
} as const;
248247
const pkgInfoEtc = await getPackageInfosEtc(
@@ -391,21 +390,21 @@ async function categorizeFile(path: string, getContents: GetContents): Promise<[
391390
case "md": return [pkg, { path, kind: "markdown" }];
392391
default: {
393392
const suspect = await configSuspicious(path, getContents);
394-
return [pkg, { path, kind: suspect ? "package-meta" : "package-meta-ok", ...suspect }];
393+
return [pkg, { path, kind: suspect ? "package-meta" : "package-meta-ok", suspect }];
395394
}
396395
}
397396
}
398397

399398
interface ConfigSuspicious {
400-
(path: string, getContents: GetContents): Promise<{ suspect: string, sugestion?: Suggestion } | undefined>;
401-
[basename: string]: (newText: string, getContents: GetContents) => Promise<{ suspect: string, suggestion?: Suggestion } | undefined>;
399+
(path: string, getContents: GetContents): Promise<Explanation | undefined>;
400+
[basename: string]: (newText: string, getContents: GetContents) => Promise<Explanation | undefined>;
402401
}
403402
const configSuspicious = <ConfigSuspicious>(async (path, getContents) => {
404403
const basename = path.replace(/.*\//, "");
405404
const tester = configSuspicious[basename];
406-
if (!tester) return { suspect: `edited` };
405+
if (!tester) return { body: `edited` };
407406
const newText = await getContents("head");
408-
if (newText === undefined) return { suspect: `couldn't fetch contents` };
407+
if (newText === undefined) return { body: `couldn't fetch contents` };
409408
return tester(newText, getContents);
410409
});
411410
configSuspicious["OTHER_FILES.txt"] = async () => undefined;
@@ -443,23 +442,20 @@ configSuspicious["tsconfig.json"] = makeJsonCheckerFromCore(
443442
function makeJsonCheckerFromCore(requiredForm: any, ignoredKeys: string[], requiredFormUrl: string) {
444443
return async (newText: string, getContents: GetContents) => {
445444
let suggestion: any;
446-
try { suggestion = JSON.parse(newText); } catch (e) { if (e instanceof SyntaxError) return { suspect: `couldn't parse json: ${e.message}` }; }
445+
try { suggestion = JSON.parse(newText); } catch (e) { if (e instanceof SyntaxError) return { body: `couldn't parse json: ${e.message}` }; }
447446
const newJson = jsonDiff.deepClone(suggestion);
448447
jsonDiff.applyPatch(newJson, ignoredKeys.map(path => ({ op: "remove", path })));
449448
const towardsIt = jsonDiff.deepClone(requiredForm);
450449
// Getting closer to the required form relative to master isn't
451450
// suspect
452451
const vsMaster = await ignoreExistingDiffs("master");
453452
if (!vsMaster) return undefined;
454-
if (vsMaster.done) return { suspect: vsMaster.suspect };
453+
if (vsMaster.done) return { body: vsMaster.suspect };
455454
// whereas getting closer relative to existing suggestions means
456455
// no new suggestions
457-
if (!await ignoreExistingDiffs("latestSuggestions")) return { suspect: vsMaster.suspect };
456+
if (!await ignoreExistingDiffs("latestExplanations")) return { body: vsMaster.suspect };
458457
jsonDiff.applyPatch(suggestion, jsonDiff.compare(newJson, towardsIt));
459-
return {
460-
suspect: vsMaster.suspect,
461-
suggestion: makeJsonSuggestion(),
462-
};
458+
return makeJsonSuggestion();
463459

464460
// Apply any preexisting diffs to towardsIt
465461
async function ignoreExistingDiffs(ref: keyof Refs) {
@@ -505,7 +501,7 @@ function makeJsonCheckerFromCore(requiredForm: any, ignoredKeys: string[], requi
505501
return {
506502
startLine,
507503
endLine,
508-
text: suggestionLines.join(""),
504+
body: vsMaster!.suspect + "\n```suggestion\n" + suggestionLines.join("") + "```",
509505
};
510506
}
511507
};

0 commit comments

Comments
 (0)