Skip to content

Commit

Permalink
Add commit checkers and their tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Wei Sun authored and Wei Sun committed Sep 7, 2021
1 parent 02a194e commit cf4426e
Show file tree
Hide file tree
Showing 21 changed files with 519 additions and 6 deletions.
2 changes: 1 addition & 1 deletion DangerfileCoverage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation
let danger = Danger()

if FileManager.default.fileExists(atPath: ".build/debug/codecov") {
Coverage.spmCoverage(minimumCoverage: 60)
Coverage.spmCoverage(minimumCoverage: 70)
} else {
warn("Cannot find SPM code coverage report, please run `swift test --enable-code-coverage` before danger.")
}
5 changes: 1 addition & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ let package = Package(
.target(name: "DangerSwiftCommitLint", dependencies: [.product(name: "Danger", package: "danger-swift")]),
.testTarget(
name: "DangerSwiftCommitLintTests",
dependencies: [
"DangerSwiftCommitLint",
.product(name: "DangerFixtures", package: "danger-swift"),
]
dependencies: ["DangerSwiftCommitLint"]
),
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

struct BodyEmptyLineCheck: CommitChecker {
static var warningMessage = "Please separate commit message subject from body with newline."

private let bodyLinesOfText: [String]

init(message: CommitMessage) {
bodyLinesOfText = message.bodyLinesOfText
}

var fail: Bool {
bodyLinesOfText.isEmpty ? false : bodyLinesOfText.first?.isEmpty == false
}
}
16 changes: 16 additions & 0 deletions Sources/DangerSwiftCommitLint/CommitChecker/CommitChecker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

protocol CommitChecker: Hashable {
static var warningMessage: String { get }
var fail: Bool { get }

init(message: CommitMessage)

static func fail(message: CommitMessage) -> Bool
}

extension CommitChecker {
static func fail(message: CommitMessage) -> Bool {
Self(message: message).fail
}
}
28 changes: 28 additions & 0 deletions Sources/DangerSwiftCommitLint/CommitChecker/CommitMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Danger
import Foundation

struct CommitMessage: Hashable {
/// First line of the commit message
let subject: String
/// Rest of the commit message
let bodyLinesOfText: [String]
// Commit SHA value
let sha: String

init(
subject: String,
bodyLinesOfText: [String],
sha: String
) {
self.subject = subject
self.bodyLinesOfText = bodyLinesOfText
self.sha = sha
}

init(_ commit: GitHub.Commit) {
let commitMessageLines = commit.commit.message.components(separatedBy: .newlines)
subject = commitMessageLines.first ?? ""
bodyLinesOfText = Array(commitMessageLines.dropFirst())
sha = commit.sha
}
}
15 changes: 15 additions & 0 deletions Sources/DangerSwiftCommitLint/CommitChecker/SubjectCapCheck.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

struct SubjectCapCheck: CommitChecker {
static let warningMessage = "Please start commit message subject with capital letter."

private let firstCharacter: Character?

init(message: CommitMessage) {
firstCharacter = message.subject.first
}

var fail: Bool {
firstCharacter?.isLowercase ?? false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation

struct SubjectLengthCheck: CommitChecker {
private enum GeneratedSubjectPattern {
static let git = #"^Merge branch '.+' into "#
static let gitHub = #"^Merge pull request #\d+ from "#
}

static let warningMessage = "Please limit commit message subject line to 50 characters."

private let subject: String

init(message: CommitMessage) {
subject = message.subject
}

var fail: Bool {
subject.count > 50 && isMergeCommit == false
}
}

private extension SubjectLengthCheck {
var isMergeCommit: Bool {
subject =~ GeneratedSubjectPattern.git || subject =~ GeneratedSubjectPattern.gitHub
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

struct SubjectPeriodCheck: CommitChecker {
static var warningMessage = "Please remove period from end of commit message subject line."

private let subject: String

init(message: CommitMessage) {
subject = message.subject
}

var fail: Bool {
subject.last == "."
}
}
15 changes: 15 additions & 0 deletions Sources/DangerSwiftCommitLint/CommitChecker/SubjectWordCheck.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

struct SubjectWordCheck: CommitChecker {
static var warningMessage = "Please use more than one word in commit message."

private let subject: String

init(message: CommitMessage) {
subject = message.subject
}

var fail: Bool {
subject.split(separator: " ").count < 2
}
}
2 changes: 1 addition & 1 deletion Sources/DangerSwiftCommitLint/DangerSwiftCommitLint.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Danger
import Foundation

final class DangerSwiftCommitLint {}
public final class DangerSwiftCommitLint {}
33 changes: 33 additions & 0 deletions Sources/DangerSwiftCommitLint/Extensions/RegexHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

infix operator =~: RegularExpressionPrecedence
precedencegroup RegularExpressionPrecedence {
associativity: none
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
}

func =~ (input: String, regexPattern: String) -> Bool {
do {
return try RegexHelper(regexPattern).match(input)
} catch {
return false
}
}

private struct RegexHelper {
private let regex: NSRegularExpression

init(_ pattern: String) throws {
regex = try NSRegularExpression(pattern: pattern, options: [])
}

func match(_ input: String) -> Bool {
regex.matches(
in: input,
options: [],
range: NSRange(location: 0, length: input.utf16.count)
)
.count > 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Danger
@testable import DangerSwiftCommitLint
import XCTest

final class BodyEmptyLineCheckTests: XCTestCase {
private let commitSubjectAndBody = CommitMessage(subject: "Title Test", bodyLinesOfText: ["", "Body Test"], sha: "Test SHA")
private let commitSubjectOnly = CommitMessage(subject: "Title Test", bodyLinesOfText: [], sha: "Test SHA")
private let commitNoNewline = CommitMessage(subject: "Title Test", bodyLinesOfText: ["Body Test"], sha: "Test SHA")

func testSuccessCommitSubjectAndBody() {
let testSubject = BodyEmptyLineCheck(message: commitSubjectAndBody)
XCTAssertFalse(testSubject.fail)
XCTAssertFalse(BodyEmptyLineCheck.fail(message: commitSubjectAndBody))
}

func testSuccessSubjectOnly() {
let testSubject = BodyEmptyLineCheck(message: commitSubjectOnly)
XCTAssertFalse(testSubject.fail)
XCTAssertFalse(BodyEmptyLineCheck.fail(message: commitSubjectOnly))
}

func testFailureNoNewlineOnly() {
let testSubject = BodyEmptyLineCheck(message: commitNoNewline)
XCTAssertTrue(testSubject.fail)
XCTAssertTrue(BodyEmptyLineCheck.fail(message: commitNoNewline))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Danger
@testable import DangerSwiftCommitLint
import XCTest

final class CommitMessageTests: XCTestCase {
func testInitializeWithDangerGitHubCommit() {
let commit = CommitParser.parseCommitJSON(with: commitMessageJSON)
let expectedCommitMessage = commit.commit.message.components(separatedBy: .newlines)
let testSubject = CommitMessage(commit)
XCTAssertEqual(testSubject.sha, commit.sha)
XCTAssertEqual(
[
[testSubject.subject],
testSubject.bodyLinesOfText,
]
.flatMap { $0 },
expectedCommitMessage
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Danger
import Foundation

enum CommitParser {
static func parseCommitJSON(with commitJSON: String) -> GitHub.Commit {
let data = Data(commitJSON.utf8)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(DateFormatter.dateFormatterHandler)
return try! decoder.decode(GitHub.Commit.self, from: data) // swiftlint:disable:this force_try
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
let commitMessageJSON = """
{
"sha": "93ae30cf2aee4241c442fb3242543490998cffdb",
"commit": {
"author": {
"name": "Ash Furrow",
"email": "[email protected]",
"date": "2016-07-26T19:54:16Z"
},
"committer": {
"name": "Ash Furrow",
"email": "[email protected]",
"date": "2016-07-26T19:55:00Z"
},
"message": "[Xcode] Updates for compatibility with Xcode 7.3.1.\\n\\nTest only",
"tree": {
"sha": "fb6bc3fda2456c5ff0a4e8f307f24ee73f281fc1",
"url": "https://api.github.com/repos/artsy/eidolon/git/trees/fb6bc3fda2456c5ff0a4e8f307f24ee73f281fc1"
},
"url": "https://api.github.com/repos/artsy/eidolon/git/commits/93ae30cf2aee4241c442fb3242543490998cffdb",
"comment_count": 0,
"verification": {
"verified": true,
"reason": "valid",
"signature": "-----BEGIN PGP SIGNATURE-----\\nVersion: GnuPG v1\\n\\niQEcBAABAgAGBQJXl8AUAAoJEAGZOscENF/tIA8H/Ri9VdHJAzfO1aAtnoQ5W8Kw\\n1yYd5BTVnr0nVw95qxBgoRbBLMUIKg0TOPQQa1h7hk6SO0py6 E4HSpCJQq97f8J\\nvgeiFHuyfcW/ePSS8WwJbIzTP3xkckvdZIPjXM1KtvzQ1vCoOrOwBxMqH2twoTQk\\nuGd5cgfsahUGHcwYA6B4vfkmAGLkOyFVjUzbgf1n T5CMbPVlbFgss3aEi8Ql81S\\ncNjtMGiUm9n3LUG5lMiwOC3898fpE8YYoAPy1CtLuwokGws3Tu9jMSnUCi2Al7KC\\nzWMpIS3L2WVoCdhiv2NbXxUDTban8ll KGdtzw3QLZ0AL5ZEkuKrxtDQGyimpaw=\\n=aGrl\\n-----END PGP SIGNATURE-----",
"payload": "tree fb6bc3fda2456c5ff0a4e8f307f24ee73f281fc1\\nparent 68c8db83776c1942145f530159a3fffddb812577\\nauthor AshFurrow <[email protected]> 1469562856 -0400\\ncommitter Ash Furrow <[email protected]> 1469562900 -0400\\n\\n[Xcode]Updates for compatibility with Xcode 7.3.1.\\n"
}
},
"url": "https://api.github.com/repos/artsy/eidolon/commits/93ae30cf2aee4241c442fb3242543490998cffdb",
"html_url": "https://github.com/artsy/eidolon/commit/93ae30cf2aee4241c442fb3242543490998cffdb",
"comments_url": "https://api.github.com/repos/artsy/eidolon/commits/93ae30cf2aee4241c442fb3242543490998cffdb/comments",
"author": {
"login": "ashfurrow",
"id": 498212,
"avatar_url": "https://avatars3.githubusercontent.com/u/498212?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/ashfurrow",
"html_url": "https://github.com/ashfurrow",
"followers_url": "https://api.github.com/users/ashfurrow/followers",
"following_url": "https://api.github.com/users/ashfurrow/following{/other_user}",
"gists_url": "https://api.github.com/users/ashfurrow/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ashfurrow/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ashfurrow/subscriptions",
"organizations_url": "https://api.github.com/users/ashfurrow/orgs",
"repos_url": "https://api.github.com/users/ashfurrow/repos",
"events_url": "https://api.github.com/users/ashfurrow/events{/privacy}",
"received_events_url": "https://api.github.com/users/ashfurrow/received_events",
"type": "User",
"site_admin": false
},
"committer": {
"login": "ashfurrow",
"id": 498212,
"avatar_url": "https://avatars3.githubusercontent.com/u/498212?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/ashfurrow",
"html_url": "https://github.com/ashfurrow",
"followers_url": "https://api.github.com/users/ashfurrow/followers",
"following_url": "https://api.github.com/users/ashfurrow/following{/other_user}",
"gists_url": "https://api.github.com/users/ashfurrow/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ashfurrow/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ashfurrow/subscriptions",
"organizations_url": "https://api.github.com/users/ashfurrow/orgs",
"repos_url": "https://api.github.com/users/ashfurrow/repos",
"events_url": "https://api.github.com/users/ashfurrow/events{/privacy}",
"received_events_url": "https://api.github.com/users/ashfurrow/received_events",
"type": "User",
"site_admin": false
},
"parents": [
{
"sha": "68c8db83776c1942145f530159a3fffddb812577",
"url": "https://api.github.com/repos/artsy/eidolon/commits/68c8db83776c1942145f530159a3fffddb812577",
"html_url": "https://github.com/artsy/eidolon/commit/68c8db83776c1942145f530159a3fffddb812577"
}
]
}
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@testable import DangerSwiftCommitLint
import XCTest

final class SubjectCapCheckTests: XCTestCase {
func testSuccess() {
let commitMessage = CommitMessage(subject: "Subject", bodyLinesOfText: [], sha: "Test SHA")
let testSubject = SubjectCapCheck(message: commitMessage)
XCTAssertFalse(testSubject.fail)
XCTAssertFalse(SubjectCapCheck.fail(message: commitMessage))
}

func testFailure() {
let commitMessage = CommitMessage(subject: "subject", bodyLinesOfText: [], sha: "Test SHA")
let testSubject = SubjectCapCheck(message: commitMessage)
XCTAssertTrue(testSubject.fail)
XCTAssertTrue(SubjectCapCheck.fail(message: commitMessage))
}

func testSuccessWithNonLetterCharacters() {
let commitMessage = CommitMessage(subject: "[TEST-123] Subject", bodyLinesOfText: [], sha: "Test SHA")
let testSubject = SubjectCapCheck(message: commitMessage)
XCTAssertFalse(testSubject.fail)
XCTAssertFalse(SubjectCapCheck.fail(message: commitMessage))
}

/// Git generally disallows empty commit message, so subject should at least contain non empty text. This test case is only capturing a unlikely edge case.
func testEdgeCase() {
let commitMessage = CommitMessage(subject: "", bodyLinesOfText: [], sha: "Test SHA")
let testSubject = SubjectCapCheck(message: commitMessage)
XCTAssertFalse(testSubject.fail)
XCTAssertFalse(SubjectCapCheck.fail(message: commitMessage))
}
}
Loading

0 comments on commit cf4426e

Please sign in to comment.