Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions src/github/manifests/home-assistant-io/rules/docs-target-branch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { extractAllLinks } from "../../../../util/pr-body.js";
import { EventType } from "../../../engine/event.js";
import type { RuleContext } from "../../../engine/model/rule-context.js";
import { type CheckOutcome, check } from "../../../engine/rule.js";
Expand All @@ -12,15 +13,41 @@ type HandledEvent =
| EventType.PULL_REQUEST_EDITED
| EventType.ON_DEMAND;

// Each named PR costs a read; a description names its parent near the top.
const MAX_NAMED = 3;

/**
* Whether the PR documents a code PR: one listed on the template line, linked
* back or not, or one named anywhere else in the description. An issue
* doesn't count.
*/
async function documentsCodePR(ctx: RuleContext<HandledEvent>): Promise<boolean> {
if ((await linkedPRs(ctx, homeAssistantPrLinks)).some((l) => l.relation === "parent"))
return true;

const self = { owner: ctx.repo.owner, repo: ctx.repo.name };
const named = extractAllLinks(await ctx.target.body())
.filter((ref) => homeAssistantPrLinks.relationOf(self, ref) === "parent")
.slice(0, MAX_NAMED);
for (const ref of named) {
try {
await ctx.pullRequest(ref).baseRepo();
return true;
} catch (err) {
if ((err as { status?: number }).status !== 404) throw err;
}
}
return false;
}

async function evaluate(ctx: RuleContext<HandledEvent>): Promise<CheckOutcome | undefined> {
const baseRef = await ctx.target.baseRef();
if (EXEMPT_BRANCHES.has(baseRef)) {
return { status: "skip", message: `PRs targeting \`${baseRef}\` are exempt.` };
}

// Documenting a code PR, listed back or not, is documenting an upcoming release.
const linked = await linkedPRs(ctx, homeAssistantPrLinks);
const expected = linked.some((l) => l.relation === "parent") ? "next" : "current";
// Documenting a code PR is documenting an upcoming release.
const expected = (await documentsCodePR(ctx)) ? "next" : "current";
if (baseRef === expected) {
return { status: "pass", message: `This PR targets \`${expected}\`.` };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,25 @@ describe("docs-target-branch", () => {
`${LINE} home-assistant/brands#1 home-assistant/home-assistant.io#2`,
["brands#1", "home-assistant.io#2"],
],
["a code PR outside the line", "Parent: home-assistant/core#12345", ["core#12345"]],
["a code issue named in the description", "Fixes the issue in home-assistant/core#7", []],
["a PR of a repo io doesn't follow", "Parent: home-assistant/android#3", ["android#3"]],
])("has no parent in %s", async (_, body, prs) => {
const result = await runRule(docsTargetBranch, docsContext("current", body, prs));
expect(result?.section?.status).toBe("pass");
});

it.each([
["in prose", "This documents home-assistant/core#12345."],
[
"on a line without a space after the colon",
"- Link to parent pull request in the codebase:https://github.com/home-assistant/core/pull/12345",
],
["on an indented line", " - Parent: https://github.com/home-assistant/core/pull/12345"],
])("expects next for a code PR named %s", async (_, body) => {
const result = await runRule(docsTargetBranch, docsContext("next", body, ["core#12345"]));
expect(result?.section?.status).toBe("pass");
});

it("exempts the `new` branch", async () => {
const result = await runRule(docsTargetBranch, docsContext("new"));
expect(result?.section?.status).toBe("skip");
Expand Down
Loading