Skip to content
This repository has been archived by the owner on Jan 31, 2023. It is now read-only.

Commit

Permalink
Add the xcissues Swift package
Browse files Browse the repository at this point in the history
  • Loading branch information
orj committed Mar 25, 2022
1 parent 01af6d8 commit 00bb67e
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser.git",
"state": {
"branch": null,
"revision": "82905286cc3f0fa8adc4674bf49437cab65a8373",
"version": "1.1.1"
}
}
]
},
"version": 1
}
27 changes: 27 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// swift-tools-version:5.5
import PackageDescription

let package = Package(
name: "SwiftLint",
platforms: [.macOS(.v12)],
products: [
.executable(name: "xcissues", targets: ["xcissues"]),
],
dependencies: [
.package(name: "swift-argument-parser", url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.1.1")),
],
targets: [
.executableTarget(
name: "xcissues",
dependencies: [
"XcodeIssues",
]
),
.target(
name: "XcodeIssues",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
]
)
33 changes: 33 additions & 0 deletions Source/XcodeIssues/ReviewDogJSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

struct DiagnosticResult: Codable, Hashable {
var diagnostics: [Diagnostic]
}

struct Diagnostic: Codable, Hashable {
struct Position: Codable, Hashable {
var line: Int
var column: Int
}

struct Range: Codable, Hashable {
var start: Position
var end: Position?
}

struct Location: Codable, Hashable {
var path: String
var range: Range
}

enum Severity: String, Codable, Hashable {
case unknown = "UNKNOWN_SEVERITY"
case error = "ERROR"
case info = "INFO"
case warning = "WARNING"
}

var message: String
var location: Location
var severity: Severity
}
70 changes: 70 additions & 0 deletions Source/XcodeIssues/XcodeIssues.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import ArgumentParser
import Foundation

public struct XcodeIssues: AsyncParsableCommand {
public static var configuration = CommandConfiguration(
commandName: "xcissues",
abstract: "A utility for reporting xcode issues in reviewdog JSON Format.")

@Argument(help: "The path of the xcresult JSON file to process. If omitted, stdin is used instead.")
var input: String?

public init() {
}

public func run() throws {
let fileHandle = input.flatMap(FileHandle.init(forReadingAtPath:)) ?? .standardInput

guard let fileData = try fileHandle.readToEnd() else {
return // TODO: throw an error?
}

let invocationRecord = try JSONDecoder().decode(ActionsInvocationRecord.self, from: fileData)
let errorDiagnostics = invocationRecord.issues.errorSummaries?.values.compactMap { Diagnostic(issueSummary: $0, severity: .error) } ?? []
let warningDiagnostics = invocationRecord.issues.warningSummaries?.values.compactMap { Diagnostic(issueSummary: $0, severity: .warning) } ?? []
let allDiagnostics = errorDiagnostics + warningDiagnostics
let result = DiagnosticResult(diagnostics: allDiagnostics)
let jsonData = try JSONEncoder().encode(result)

try FileHandle.standardOutput.write(contentsOf: jsonData)
}
}

extension Diagnostic {
init?(issueSummary: IssueSummary, severity: Severity) {
guard let location = issueSummary.documentLocationInCreatingWorkspace,
let startingLineNumber = location.startingLineNumber,
let startingColumnNumber = location.startingColumnNumber
else {
return nil
}

// `DocumentLocation` uses zero-indexed line/column numbers, while rdjson uses 1-indexed line/column numbers.
// Add 1 to each of the line/column numbers here to account for this.
let endPosition: Position?
if let endingLineNumber = location.endingLineNumber,
let endingColumnNumber = location.endingColumnNumber {
endPosition = .init(
line: endingLineNumber + 1,
column: endingColumnNumber + 1
)
} else {
endPosition = nil
}

self.init(
message: issueSummary.message.value,
location: .init(
path: location.path,
range: .init(
start: .init(
line: startingLineNumber + 1,
column: startingColumnNumber + 1
),
end: endPosition
)
),
severity: severity
)
}
}
81 changes: 81 additions & 0 deletions Source/XcodeIssues/XcodeResultTypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation

public struct XcodeObject<Value: Decodable>: Decodable {
enum CodingKeys: String, CodingKey {
case value = "_value"
}

public let value: Value
}

public struct XcodeArray<Value: Decodable>: Decodable {
enum CodingKeys: String, CodingKey {
case values = "_values"
}

public let values: [Value]
}

public struct ActionsInvocationRecord: Decodable {
public let issues: ResultIssueSummaries
}

public struct ResultIssueSummaries: Decodable {
public var errorSummaries: XcodeArray<IssueSummary>?
public var warningSummaries: XcodeArray<IssueSummary>?
public var testFailureSummaries: XcodeArray<TestFailureIssueSummary>?
}

public struct IssueSummary: Decodable {
public let documentLocationInCreatingWorkspace: DocumentLocation?
public var message: XcodeObject<String>
}

public struct TestFailureIssueSummary: Decodable {
public let documentLocationInCreatingWorkspace: DocumentLocation?
public var message: XcodeObject<String>
public var producingTarget: XcodeObject<String>
public var testCaseName: XcodeObject<String>
}

public struct DocumentLocation: Decodable {
public let path: String
public let startingLineNumber: Int?
public let endingLineNumber: Int?
public let startingColumnNumber: Int?
public let endingColumnNumber: Int?

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let url = try container.decode(XcodeObject<URL>.self, forKey: .url)
guard var urlComponents = URLComponents(url: url.value, resolvingAgainstBaseURL: true) else {
throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "Invalid URL")
}

urlComponents.query = urlComponents.fragment
let fragmentQueryItem = { name in
urlComponents
.fragmentQueryItems?
.last(where: { $0.name == name })?
.value
}

self.path = urlComponents.path
self.startingLineNumber = fragmentQueryItem("StartingLineNumber").flatMap(Int.init)
self.endingLineNumber = fragmentQueryItem("EndingLineNumber").flatMap(Int.init)
self.startingColumnNumber = fragmentQueryItem("StartingColumnNumber").flatMap(Int.init)
self.endingColumnNumber = fragmentQueryItem("EndingColumnNumber").flatMap(Int.init)
}

enum CodingKeys: String, CodingKey {
case url = "url"
}
}

extension URLComponents {
var fragmentQueryItems: [URLQueryItem]? {
var newComponents = URLComponents()
newComponents.query = fragment
return newComponents.queryItems
}
}
8 changes: 8 additions & 0 deletions Source/xcissues/MainApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import XcodeIssues

@main
struct MainApp {
static func main() {
XcodeIssues.main()
}
}

0 comments on commit 00bb67e

Please sign in to comment.