Skip to content
Merged
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
71 changes: 71 additions & 0 deletions src/domains/rules/NoMergeCommits.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest"
import { fakeCommitFactory } from "#commits/Commit.fixtures.ts"
import { fakeConfiguration } from "#configurations/Configuration.fixtures.ts"
import { commitConcern } from "#rules/concerns/CommitConcern.ts"
import type { Concerns } from "#rules/concerns/Concern.ts"
import { noMergeCommits } from "#rules/NoMergeCommits.ts"
import { ruleContext } from "#rules/Rule.ts"
import { fakeCommitSha } from "#types/CommitSha.fixtures.ts"
import type { CommitSha } from "#types/CommitSha.ts"

const enabled = ruleContext("noMergeCommits")

const fakeCommit = fakeCommitFactory(fakeConfiguration())

describe.each`
parents | subjectLine
${[fakeCommitSha(), fakeCommitSha()]} | ${"Merge branch 'main' into bugfix/dance-party-playlist"}
${[fakeCommitSha(), fakeCommitSha()]} | ${"Merge pull request #440 from renovate/battle-shell-7.x"}
${[fakeCommitSha(), fakeCommitSha(), fakeCommitSha()]} | ${"amend! Merge branch 'feature/new-coffee-machine' into feature/office-overhaul"}
${[fakeCommitSha(), fakeCommitSha(), fakeCommitSha()]} | ${"Keep my branch up to date"}
`(
"when the commit is a merge commit with $parents.length parents and has a subject line of $subjectLine",
(props: { parents: Array<CommitSha>; subjectLine: string }) => {
const commit = fakeCommit({ message: props.subjectLine, parents: props.parents })

describe("and the rule is enabled", () => {
const actualConcerns = noMergeCommits([commit], enabled.options)

it("raises a concern about the entire commit", () => {
expect(actualConcerns).toEqual<Concerns>([commitConcern(enabled, commit.sha)])
})
})

describe("and the rule is disabled", () => {
const actualConcerns = noMergeCommits([commit], null)

it("does not raise any concerns", () => {
expect(actualConcerns).toEqual<Concerns>([])
})
})
},
)

describe.each`
parents | subjectLine
${[]} | ${"init"}
${[]} | ${"Establish the repository"}
${[fakeCommitSha()]} | ${"just an ordinary commit"}
${[fakeCommitSha()]} | ${"Release the robot butler"}
`(
"when the commit is not a merge commit and has a subject line of $subjectLine",
(props: { parents: Array<CommitSha>; subjectLine: string }) => {
const commit = fakeCommit({ message: props.subjectLine, parents: props.parents })

describe("and the rule is enabled", () => {
const actualConcerns = noMergeCommits([commit], enabled.options)

it("does not raise any concerns", () => {
expect(actualConcerns).toEqual<Concerns>([])
})
})

describe("and the rule is disabled", () => {
const actualConcerns = noMergeCommits([commit], null)

it("does not raise any concerns", () => {
expect(actualConcerns).toEqual<Concerns>([])
})
})
},
)
15 changes: 13 additions & 2 deletions src/domains/rules/NoMergeCommits.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { Commit, Commits } from "#commits/Commit.ts"
import { commitConcern } from "#rules/concerns/CommitConcern.ts"
import type { Concern, Concerns } from "#rules/concerns/Concern.ts"
import { type RuleContext, ruleContext } from "#rules/Rule.ts"
import type { EmptyObject } from "#types/EmptyObject.ts"
import { notNullish } from "#utilities/Arrays.ts"

/**
* Verifies that the commit has at most one parent commit, thus disallowing merge commits.
*
* Avoiding merge commits makes the commit history linear and preserves the ability to rebase interactively.
* This helps to preserve the readability of the commit history and makes it easier to revert changes later.
*/
export function noMergeCommits(commits: Commits, options: EmptyObject | null): Concerns {
if (options === null) {
return []
Expand All @@ -13,6 +20,10 @@ export function noMergeCommits(commits: Commits, options: EmptyObject | null): C
return commits.map((commit) => verifyCommit(commit, rule)).filter(notNullish)
}

function verifyCommit(_commit: Commit, _rule: RuleContext): Concern | null {
throw new Error("The `noMergeCommits` rule has not been implemented yet") // TODO: To be implemented.
function verifyCommit(commit: Commit, rule: RuleContext): Concern | null {
if (commit.isMergeCommit) {
return commitConcern(rule, commit.sha)
}

return null
}
2 changes: 1 addition & 1 deletion src/domains/rules/NoSquashMarkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { EmptyObject } from "#types/EmptyObject.ts"
import { notNullish } from "#utilities/Arrays.ts"

/**
* Verifies that the subject line does not contain squash markers.
* Verifies that the subject line does not contain any squash marker.
*
* Combining squash commits with their ancestors makes the commit history cleaner and easier to read,
* as it omits unnecessary diffs and increases the cohesion of commits. It also makes it easier to revert changes later.
Expand Down
72 changes: 48 additions & 24 deletions src/domains/rules/UseConciseSubjectLines.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,39 @@ describe.each`
const commit = fakeCommit({ message: props.subjectLine })

describe("and the rule is enabled with a maximum length of 20 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled20.options)

it("raises a concern about the characters that exceed the limit", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled20.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled20, commit.sha, { range: props.expectedRange20 }),
])
})
})

describe("and the rule is enabled with a maximum length of 50 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled50.options)

it("raises a concern about the characters that exceed the limit", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled50.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled50, commit.sha, { range: props.expectedRange50 }),
])
})
})

describe("and the rule is enabled with a maximum length of 72 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled72.options)

it("raises a concern about the characters that exceed the limit", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled72.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled72, commit.sha, { range: props.expectedRange72 }),
])
})
})

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines([commit], null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand All @@ -84,33 +88,37 @@ describe.each`
const commit = fakeCommit({ message: props.subjectLine })

describe("and the rule is enabled with a maximum length of 20 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled20.options)

it("raises a concern about the characters that exceed the limit", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled20.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled20, commit.sha, { range: props.expectedRange20 }),
])
})
})

describe("and the rule is enabled with a maximum length of 50 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled50.options)

it("raises a concern about the characters that exceed the limit", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled50.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled50, commit.sha, { range: props.expectedRange50 }),
])
})
})

describe("and the rule is enabled with a maximum length of 72 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled72.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled72.options)
expect(actualConcerns).toEqual<Concerns>([])
})
})

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines([commit], null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand All @@ -135,31 +143,35 @@ describe.each`
const commit = fakeCommit({ message: props.subjectLine })

describe("and the rule is enabled with a maximum length of 20 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled20.options)

it("raises a concern about the characters that exceed the limit", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled20.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled20, commit.sha, { range: props.expectedRange20 }),
])
})
})

describe("and the rule is enabled with a maximum length of 50 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled50.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled50.options)
expect(actualConcerns).toEqual<Concerns>([])
})
})

describe("and the rule is enabled with a maximum length of 72 characters", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled72.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], enabled72.options)
expect(actualConcerns).toEqual<Concerns>([])
})
})

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines([commit], null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand All @@ -185,16 +197,18 @@ describe.each`
`(
"and the rule is enabled with a maximum length of $options.maxLength characters",
(context: RuleContext<"useConciseSubjectLines">) => {
const actualConcerns = useConciseSubjectLines([commit], context.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], context.options)
expect(actualConcerns).toEqual<Concerns>([])
})
},
)

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines([commit], null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand All @@ -218,16 +232,18 @@ describe.each`
`(
"and the rule is enabled with a maximum length of $options.maxLength characters",
(context: RuleContext<"useConciseSubjectLines">) => {
const actualConcerns = useConciseSubjectLines([commit], context.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], context.options)
expect(actualConcerns).toEqual<Concerns>([])
})
},
)

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines([commit], null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand Down Expand Up @@ -257,16 +273,18 @@ describe.each`
`(
"and the rule is enabled with a maximum length of $options.maxLength characters",
(context: RuleContext<"useConciseSubjectLines">) => {
const actualConcerns = useConciseSubjectLines([commit], context.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], context.options)
expect(actualConcerns).toEqual<Concerns>([])
})
},
)

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines([commit], null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines([commit], null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand Down Expand Up @@ -299,8 +317,9 @@ describe("when verifying a set of multiple commits and some commits have long su
]

describe("and the rule is enabled with a maximum length of 20 characters", () => {
const actualConcerns = useConciseSubjectLines(commits, enabled20.options)

it("raises concerns about the commits whose subject lines exceed 20 characters", () => {
const actualConcerns = useConciseSubjectLines(commits, enabled20.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled20, commits[0].sha, { range: [20, 52] }),
subjectLineConcern(enabled20, commits[1].sha, { range: [20, 24] }),
Expand All @@ -315,8 +334,9 @@ describe("when verifying a set of multiple commits and some commits have long su
})

describe("and the rule is enabled with a maximum length of 50 characters", () => {
const actualConcerns = useConciseSubjectLines(commits, enabled50.options)

it("raises concerns about the commits whose subject lines exceed 50 characters", () => {
const actualConcerns = useConciseSubjectLines(commits, enabled50.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled50, commits[0].sha, { range: [50, 52] }),
subjectLineConcern(enabled50, commits[7].sha, { range: [50, 75] }),
Expand All @@ -326,8 +346,9 @@ describe("when verifying a set of multiple commits and some commits have long su
})

describe("and the rule is enabled with a maximum length of 72 characters", () => {
const actualConcerns = useConciseSubjectLines(commits, enabled72.options)

it("raises concerns about the commits whose subject lines exceed 72 characters", () => {
const actualConcerns = useConciseSubjectLines(commits, enabled72.options)
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled72, commits[7].sha, { range: [72, 75] }),
subjectLineConcern(enabled72, commits[11].sha, { range: [72, 76] }),
Expand All @@ -336,8 +357,9 @@ describe("when verifying a set of multiple commits and some commits have long su
})

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines(commits, null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines(commits, null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand All @@ -359,16 +381,18 @@ describe("when verifying a set of multiple commits and all commits have concise
`(
"and the rule is enabled with a maximum length of $options.maxLength characters",
(context: RuleContext<"useConciseSubjectLines">) => {
const actualConcerns = useConciseSubjectLines(commits, context.options)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines(commits, context.options)
expect(actualConcerns).toEqual<Concerns>([])
})
},
)

describe("and the rule is disabled", () => {
const actualConcerns = useConciseSubjectLines(commits, null)

it("does not raise any concerns", () => {
const actualConcerns = useConciseSubjectLines(commits, null)
expect(actualConcerns).toEqual<Concerns>([])
})
})
Expand Down
12 changes: 12 additions & 0 deletions src/domains/rules/concerns/CommitConcern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { RuleContext } from "#rules/Rule.ts"
import type { CommitSha } from "#types/CommitSha.ts"

export type CommitConcern = {
location: "commit"
rule: RuleContext
commitSha: CommitSha
}

export function commitConcern(rule: RuleContext, commitSha: CommitSha): CommitConcern {
return { location: "commit", rule, commitSha }
}
2 changes: 2 additions & 0 deletions src/domains/rules/concerns/Concern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Configuration } from "#configurations/Configuration.ts"
import type { AuthorEmailAddressConcern } from "#rules/concerns/AuthorEmailAddressConcern.ts"
import type { AuthorNameConcern } from "#rules/concerns/AuthorNameConcern.ts"
import type { BodyLineConcern } from "#rules/concerns/BodyLineConcern.ts"
import type { CommitConcern } from "#rules/concerns/CommitConcern.ts"
import type { CommitterEmailAddressConcern } from "#rules/concerns/CommitterEmailAddressConcern.ts"
import type { CommitterNameConcern } from "#rules/concerns/CommitterNameConcern.ts"
import type { SubjectLineConcern } from "#rules/concerns/SubjectLineConcern.ts"
Expand Down Expand Up @@ -32,6 +33,7 @@ export type Concern =
| AuthorEmailAddressConcern
| AuthorNameConcern
| BodyLineConcern
| CommitConcern
| CommitterEmailAddressConcern
| CommitterNameConcern
| SubjectLineConcern
Expand Down
Loading
Loading