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
25 changes: 23 additions & 2 deletions src/domains/commits/tokens/RevertMarkerToken.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe.each`
${'revert "Fix a nasty bug"'} | ${[revertMarker('revert "'), 'Fix a nasty bug"']}
${'REVERT "Refactor the authentication module"'} | ${[revertMarker('REVERT "'), 'Refactor the authentication module"']}
${' Revert " Apply strawberry jam to make the code sweeter" '} | ${[revertMarker(' Revert "'), ' Apply strawberry jam to make the code sweeter" ']}
${' revert " revert "Find a new court jester to blame " " '} | ${[revertMarker(' revert " revert "'), 'Find a new court jester to blame " " ']}
${'Revert "Make the program act like a clown"'} | ${[revertMarker('Revert "'), 'Make the program act like a clown"']}
${'Revert "Upgrade React to 19.2.0 (#42)"'} | ${[revertMarker('Revert "'), "Upgrade React to ", dependencyVersion("19.2.0"), issueLink(" (#42)"), '"']}
${'fixup! Revert "Add an amazing feature"'} | ${[squashMarker("fixup! "), revertMarker('Revert "'), 'Add an amazing feature"']}
Expand All @@ -34,6 +35,25 @@ describe.each`
},
)

describe.each`
subjectLine | expectedTokens
${'Revert "Repair the soft ice machine'} | ${[revertMarker('Revert "'), "Repair the soft ice machine"]}
${'Revert "Revert "Repair the soft ice machine"'} | ${[revertMarker('Revert "Revert "'), 'Repair the soft ice machine"']}
${'Revert "Revert "Revert "Repair the soft ice machine'} | ${[revertMarker('Revert "Revert "Revert "'), "Repair the soft ice machine"]}
${' revert " revert "Find a new court jester to blame " '} | ${[revertMarker(' revert " revert "'), 'Find a new court jester to blame " ']}
${'fixup! Revert "Add an amazing feature'} | ${[squashMarker("fixup! "), revertMarker('Revert "'), "Add an amazing feature"]}
`(
"when the subject line of $subjectLine contains revert markers with inconsistent pairs of quotes",
(props: { subjectLine: string; expectedTokens: TokenisedLine }) => {
const crudeCommit = fakeCrudeCommit({ message: props.subjectLine })

it("extracts revert marker tokens", () => {
const commit = mapCrudeCommitToCommit(crudeCommit, configuration)
expect(commit.subjectLine).toEqual(props.expectedTokens)
})
},
)

describe.each`
subjectLine | expectedTokens
${'#1 Revert "Add an amazing feature"'} | ${[issueLink("#1 "), 'Revert "Add an amazing feature"']}
Expand All @@ -58,8 +78,9 @@ describe.each`
${'Revert "'}
${'Revert ""'}
${"Revert 'the next big thing'"}
${"Revert some more stuff"}
${"Reverted the secret stuff"}
${'revert more stuff"'}
${"Reverted some secret stuff"}
${'Revert ""weirdly quoted message'}
${'"Revert "Make the formatter happy again""'}
${'Revert"without-space"'}
${'fix: Revert "something"'}
Expand Down
3 changes: 2 additions & 1 deletion src/domains/commits/tokens/RevertMarkerToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export function isRevertMarker(token: Token): token is RevertMarkerToken {
return typeof token === "object" && token.type === "revert-marker"
}

const regex = /^\s*(?:revert\s+")+(?=[^"])/iu
// Assume all revert markers to contain a double quote `"` followed by a non-quote character (quote pair consistency not enforced for simplicity).
const regex = /^(?:\s*revert\s+")+(?=[^"])/iu

export function tokeniseRevertMarkers(initialTokens: TokenisedLine): TokenisedLine {
const result: TokenisedLine = []
Expand Down
210 changes: 210 additions & 0 deletions src/domains/rules/NoRevertRevertCommits.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { describe, expect, it } from "vitest"
import { fakeCommitFactory } from "#commits/Commit.fixtures.ts"
import type { Commit } from "#commits/Commit.ts"
import { fakeConfiguration } from "#configurations/Configuration.fixtures.ts"
import type { Concerns } from "#rules/concerns/Concern.ts"
import { subjectLineConcern } from "#rules/concerns/SubjectLineConcern.ts"
import { noRevertRevertCommits } from "#rules/NoRevertRevertCommits.ts"
import { ruleContext } from "#rules/Rule.ts"
import type { CharacterRange } from "#types/CharacterRange.ts"
import type { Vector } from "#types/Vector.ts"

const enabled = ruleContext("noRevertRevertCommits", {})
const disabled = ruleContext("noRevertRevertCommits", null)

const fakeCommit = fakeCommitFactory(fakeConfiguration())

describe.each`
subjectLine | expectedRange
${'Revert "Revert "Fix the bug""'} | ${[0, 16]}
${'Revert "revert "Repair the soft ice machine""'} | ${[0, 17]}
${' revert " revert "Apply strawberry jam to make the code sweeter " " '} | ${[1, 18]}
${' rEvErT "REVERT "Refactor the authentication module""'} | ${[2, 20]}
${'Revert "Revert "Revert "Fix the nasty bug"""'} | ${[0, 24]}
${' revert "revert "revert "revert "Repair the soft ice machine """"'} | ${[1, 34]}
`(
"when the subject line of $subjectLine contains a revert marker with 2 or more 'revert' occurrences",
(props: { subjectLine: string; expectedRange: CharacterRange }) => {
const commit = fakeCommit({ message: props.subjectLine })

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

it("raises a concern about the revert marker", () => {
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled, commit.sha, { range: props.expectedRange }),
])
})
})

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

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

describe.each`
subjectLine
${'Revert "Repair the soft ice machine"'}
${'Revert "Fix a nasty bug"'}
${'revert "Fix a nasty bug"'}
${'REVERT "Refactor the authentication module"'}
${' Revert "Apply strawberry jam to make the code sweeter" '}
${'Revert "Make the program act like a clown"'}
${'fixup! Revert "Add an amazing feature"'}
`(
"when the subject line of $subjectLine contains a revert marker with 1 'revert' occurrence",
(props: { subjectLine: string }) => {
const commit = fakeCommit({ message: props.subjectLine })

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

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

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

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

describe.each`
subjectLine
${'fixup! Revert "Revert "Add an amazing feature""'}
${' squash!amend! revert "revert "retrieve data from the exclusive third-party service""'}
`(
"when the subject line of $subjectLine contains a squash marker and a revert marker with 2 or more 'revert' occurrences",
(props: { subjectLine: string }) => {
const commit = fakeCommit({ message: props.subjectLine })

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

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

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

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

describe.each`
subjectLine
${""}
${" "}
${"\t\t"}
${"Refactor the taxi module"}
${" Unsubscribe from the service"}
${"Formatting."}
${"WIP"}
${"Fix the bug"}
${" Hunt down the bugs "}
${"fixup! Add an amazing feature"}
${"squash! Make the program act like a clown"}
${'#7044: Revert "Unsolve the problem"'}
${"Time to revert it"}
${'Not a Revert "thing"'}
${"Revert the last change"}
${"Reverted some secret stuff"}
${'fix: Revert "something"'}
`(
"when the subject line of $subjectLine does not contain a revert marker",
(props: { subjectLine: string }) => {
const commit = fakeCommit({ message: props.subjectLine })

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

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

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

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

describe("when verifying a set of multiple commits and some commits have revert markers with 2 or more 'revert' occurrences", () => {
const commits: Vector<Commit, 9> = [
fakeCommit({ message: 'Revert "Revert "Fix the bug""' }),
fakeCommit({ message: 'Revert "Repair the soft ice machine"' }),
fakeCommit({ message: "Refactor the taxi module" }),
fakeCommit({ message: 'squash! Revert "Revert "Fix the bug""' }),
fakeCommit({ message: "WIP" }),
fakeCommit({ message: ' revert "revert "WIP""' }),
fakeCommit({ message: 'Revert "Revert "Revert "Add an amazing feature"""' }),
fakeCommit({ message: " Unsubscribe from the service" }),
fakeCommit({ message: "Fix the bug" }),
]

describe("and the rule is enabled", () => {
const actualConcerns = noRevertRevertCommits(commits, enabled.options)

it("raises concerns about the commits with double revert markers", () => {
expect(actualConcerns).toEqual<Concerns>([
subjectLineConcern(enabled, commits[0].sha, { range: [0, 16] }),
subjectLineConcern(enabled, commits[5].sha, { range: [1, 17] }),
subjectLineConcern(enabled, commits[6].sha, { range: [0, 24] }),
])
})
})

describe("and the rule is disabled", () => {
const actualConcerns = noRevertRevertCommits(commits, disabled.options)

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

describe("when verifying a set of multiple commits and no commits have revert markers with 2 or more 'revert' occurrences", () => {
const commits: Vector<Commit, 6> = [
fakeCommit({ message: 'Revert "Repair the soft ice machine"' }),
fakeCommit({ message: "Fix the bug" }),
fakeCommit({ message: 'fixup! Revert "Repair the soft ice machine"' }),
fakeCommit({ message: "Time to revert it" }),
fakeCommit({ message: 'Revert "Time to revert it"' }),
fakeCommit({ message: "1 2 3 this is a test" }),
]

describe("and the rule is enabled", () => {
const actualConcerns = noRevertRevertCommits(commits, enabled.options)

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

describe("and the rule is disabled", () => {
const actualConcerns = noRevertRevertCommits(commits, disabled.options)

it("does not raise any concerns", () => {
expect(actualConcerns).toEqual<Concerns>([])
})
})
})
39 changes: 35 additions & 4 deletions src/domains/rules/NoRevertRevertCommits.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
import type { Commits } from "#commits/Commit.ts"
import type { Concerns } from "#rules/concerns/Concern.ts"
import type { Commit, Commits } from "#commits/Commit.ts"
import { isRevertMarker } from "#commits/tokens/RevertMarkerToken.ts"
import { isSquashMarker } from "#commits/tokens/SquashMarkerToken.ts"
import type { Concern, Concerns } from "#rules/concerns/Concern.ts"
import { subjectLineConcern } from "#rules/concerns/SubjectLineConcern.ts"
import { type RuleContext, ruleContext } from "#rules/Rule.ts"
import type { EmptyObject } from "#types/EmptyObject.ts"
import { notNullish } from "#utilities/Arrays.ts"

export function noRevertRevertCommits(_commits: Commits, _options: EmptyObject | null): Concerns {
throw new Error("The `noRevertRevertCommits` rule has not been implemented yet") // TODO: To be implemented.
export function noRevertRevertCommits(commits: Commits, options: EmptyObject | null): Concerns {
const rule = ruleContext("noRevertRevertCommits", options)

return options !== null
? commits.map((commit) => verifyCommit(commit, rule)).filter(notNullish)
: []
}

function verifyCommit(commit: Commit, rule: RuleContext): Concern | null {
for (const token of commit.subjectLine) {
if (isSquashMarker(token)) {
return null
}
if (isRevertMarker(token)) {
const revertOccurrences = (token.value.match(/revert/giu) ?? []).length

if (revertOccurrences > 1) {
const leadingWhitespaceOffset = token.value.length - token.value.trimStart().length
const trimmedTokenLength = token.value.trim().length

return subjectLineConcern(rule, commit.sha, {
range: [leadingWhitespaceOffset, leadingWhitespaceOffset + trimmedTokenLength],
})
}
}
}

return null
}
Loading
Loading