Skip to content

Commit

Permalink
Add basic infrastructure for XCLinter
Browse files Browse the repository at this point in the history
  • Loading branch information
aidaan committed Oct 15, 2023
1 parent b6fd67b commit 453ec0c
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 9 deletions.
15 changes: 15 additions & 0 deletions Sources/XCLinting/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import XcodeProj
import PathKit

public final class Configuration {
public let project: XcodeProj
public let projectText: String
public let projectRoot: Path

public init(project: XcodeProj, projectText: String, projectRoot: Path) {
self.project = project
self.projectText = projectText
self.projectRoot = projectRoot
}
}
2 changes: 0 additions & 2 deletions Sources/XCLinting/File.swift

This file was deleted.

4 changes: 4 additions & 0 deletions Sources/XCLinting/Rule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public protocol Rule {
init(configuration: Configuration)
func run() throws -> [Violation]
}
5 changes: 5 additions & 0 deletions Sources/XCLinting/Status.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public enum Status: Hashable {
case passed
case invalidInput
case failed
}
11 changes: 11 additions & 0 deletions Sources/XCLinting/Violation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XcodeProj

public struct Violation {
public var message: String
public var objects: [PBXObject]

public init(_ message: String, objects: [PBXObject] = []) {
self.message = message
self.objects = objects
}
}
16 changes: 16 additions & 0 deletions Sources/XCLinting/XCLintError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
public enum XCLintError: Error {
case noProjectFileSpecified
case projectFileNotFound(String)
case badProjectFile(Error)

public var localizedDescription: String {
switch self {
case .noProjectFileSpecified:
return "Project file was not specified."
case let .projectFileNotFound(path):
return "Project file not found at '\(path)'."
case let .badProjectFile(error):
return "Bad project file: \(error.localizedDescription)."
}
}
}
44 changes: 44 additions & 0 deletions Sources/XCLinting/XCLinter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Foundation
import XcodeProj
import PathKit

public struct XCLinter {
public var configuration: Configuration
public var rules: [Rule.Type]

public init(projectPath: String) throws {
guard !projectPath.isEmpty else {
throw XCLintError.noProjectFileSpecified
}

let path = Path(projectPath)
guard path.isDirectory else {
throw XCLintError.projectFileNotFound(projectPath)
}

let xcodeproj: XcodeProj
let projectText: String
do {
xcodeproj = try XcodeProj(path: path)
projectText = try String(contentsOf: URL(fileURLWithPath: projectPath).appendingPathComponent("project.pbxproj"))
} catch {
throw XCLintError.badProjectFile(error)
}

self.init(configuration: Configuration(project: xcodeproj, projectText: projectText, projectRoot: path), rules: [])
}

public init(configuration: Configuration, rules: [Rule.Type]) {
self.configuration = configuration
self.rules = rules
}

public func run() throws -> [Violation] {
var violations = [Violation]()
for ruleType in rules {
let rule = ruleType.init(configuration: configuration)
violations.append(contentsOf: try rule.run())
}
return violations
}
}
29 changes: 29 additions & 0 deletions Sources/clitool/main.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import Foundation
import ArgumentParser
import XcodeProj
import PathKit
import XCLinting

struct XCLintCommand: ParsableCommand {
static var configuration = CommandConfiguration(commandName: "xclint")
Expand All @@ -9,10 +13,35 @@ struct XCLintCommand: ParsableCommand {
)
var version: Bool = false

@Argument(help: "The path to the .xcodeproj bundle to lint.")
var projectFile: String = ""

func run() throws {
if version {
throw CleanExit.message("0.0.1")
}

let linter: XCLinter
do {
linter = try XCLinter(projectPath: projectFile)
} catch {
throw ValidationError(error.localizedDescription)
}

let violations: [Violation]
do {
violations = try linter.run()
} catch {
throw ValidationError(error.localizedDescription)
}

for violation in violations {
print(violation.message)
}

if !violations.isEmpty {
throw ExitCode.failure
}
}
}

Expand Down
7 changes: 0 additions & 7 deletions Tests/XCLintTests/XCLintTests.swift

This file was deleted.

26 changes: 26 additions & 0 deletions Tests/XCLintTests/XCLinterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import XCTest
import XCLinting

final class XCLinterTests: XCTestCase {

func testEmptyProjectPathThrowsError() throws {
do {
_ = try XCLinter(projectPath: "")
XCTFail()
} catch XCLintError.noProjectFileSpecified {
} catch {
XCTFail("wrong error: \(error)")
}
}


func testMissingProjectFileThrowsError() throws {
do {
_ = try XCLinter(projectPath: "/dev/null")
XCTFail()
} catch XCLintError.projectFileNotFound {
} catch {
XCTFail("wrong error: \(error)")
}
}
}

0 comments on commit 453ec0c

Please sign in to comment.