Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commit checkers and their tests #3

Merged
merged 3 commits into from
Sep 7, 2021
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
4 changes: 2 additions & 2 deletions .github/workflows/gha_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- run: npm install -g danger
- run: swift run danger-swift ci -c release
- run: swift run danger-swift ci -c release --id "Lint"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_SWIFT_GITHUB_TOKEN }}
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/checkout@v1
- run: npm install -g danger
- run: swift test --enable-code-coverage
- run: swift run danger-swift ci -c release --dangerfile DangerfileCoverage.swift
- run: swift run danger-swift ci -c release --dangerfile DangerfileCoverage.swift --id "Coverage"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_SWIFT_GITHUB_TOKEN }}
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
}
}
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 == "."
}
}
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
}
}
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,80 @@
// swiftlint:disable line_length
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"
}
]
}
"""
// swiftlint:enable line_length
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