1import type {
2 PullRequestComment,
3 PullRequestDetail,
4 PullRequestReviewThread,
5} from "@t3tools/contracts";
6
7/** Only the parts of a detail either answer reads, so a caller can pass a whole detail view. */
8type EditingSubject = Pick<
9 PullRequestDetail,
10 "author" | "capabilities" | "viewer" | "viewerPermissions"
11>;
12
13/** Hosts disagree about the case of a login and none of them treats two casings as two people. */
14function sameLogin(one: string | null | undefined, other: string | null | undefined): boolean {
15 if (one == null || other == null) return false;
16 return one.trim().toLowerCase() === other.trim().toLowerCase();
17}
18
19/**
20 * Whether the title and description can be rewritten from here. Beyond the host being able to at
21 * all, a host that reports `editChangeRequest` answers directly. Otherwise either the reader wrote
22 * the change request or they may merge it: merging is the one action every host here grants with
23 * write access and withholds without it, so it stands in for the permission they don't publish.
24 */
25export function canEditPullRequestChangeRequest(detail: EditingSubject): boolean {
26 if (detail.capabilities.edit?.changeRequest !== true) return false;
27 if (detail.viewerPermissions.editChangeRequest !== undefined) {
28 return detail.viewerPermissions.editChangeRequest;
29 }
30 return (
31 sameLogin(detail.viewer, detail.author?.login) ||
32 detail.viewerPermissions.actions.includes("merge")
33 );
34}
35
36/**
37 * Whether this remark can be rewritten from here. A review's own summary is left out: the hosts
38 * disagree about what one even is, and none of them takes a rewrite of it through the mutation
39 * a comment goes through.
40 */
41export function canEditPullRequestComment(
42 detail: EditingSubject,
43 comment: Pick<PullRequestComment, "author" | "canEdit" | "kind">,
44): boolean {
45 if (detail.capabilities.edit?.comment !== true) return false;
46 if (comment.kind !== "issue-comment" && comment.kind !== "review-comment") return false;
47 if (comment.canEdit !== undefined) return comment.canEdit;
48 return sameLogin(detail.viewer, comment.author?.login);
49}
50
51/**
52 * Whether the reader may resolve or reopen one review thread. The host's answer for this thread
53 * can only narrow the reader's repository-wide `resolve` permission: a thread can't grant what the
54 * repository withholds.
55 */
56export function canResolvePullRequestThread(
57 detail: Pick<PullRequestDetail, "viewerPermissions">,
58 thread: Pick<PullRequestReviewThread, "canResolve">,
59): boolean {
60 return detail.viewerPermissions.resolve && thread.canResolve !== false;
61}