Skip to content

Commit

Permalink
Merge pull request #133 from realm/jp-custom-reporters
Browse files Browse the repository at this point in the history
added custom reporters: xcode (default), json, csv. fixes #42
  • Loading branch information
jpsim committed Sep 19, 2015
2 parents f8e226d + 1bec2bc commit 3c090e1
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
* Violations are now printed to stderr.
[Keith Smiley](https://github.com/keith)

* Custom reporters are now supported. Specify a value for the `reporter:` key in
your configuration file. Available reporters are `xcode` (default), `json`,
`csv`.
[JP Simard](https://github.com/jpsim)
[#42](https://github.com/realm/SwiftLint/issues/42)

##### Bug Fixes

* Improve performance of `TrailingWhitespaceRule`.
Expand Down
23 changes: 21 additions & 2 deletions Source/SwiftLintFramework/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,33 @@ public struct Configuration {
public let disabledRules: [String] // disabled_rules
public let included: [String] // included
public let excluded: [String] // excluded
public let reporter: String // reporter (xcode, json, csv)

public var rules: [Rule] {
return allRules.filter { !disabledRules.contains($0.identifier) }
}

public init?(disabledRules: [String] = [], included: [String] = [], excluded: [String] = []) {
public var reporterFromString: Reporter.Type {
switch reporter {
case XcodeReporter.identifier:
return XcodeReporter.self
case JSONReporter.identifier:
return JSONReporter.self
case CSVReporter.identifier:
return CSVReporter.self
default:
fatalError("no reporter with identifier '\(reporter)' available.")
}
}

public init?(disabledRules: [String] = [],
included: [String] = [],
excluded: [String] = [],
reporter: String = "xcode") {
self.disabledRules = disabledRules
self.included = included
self.excluded = excluded
self.reporter = reporter

// Validate that all rule identifiers map to a defined rule

Expand Down Expand Up @@ -67,7 +85,8 @@ public struct Configuration {
self.init(
disabledRules: yamlConfig["disabled_rules"].arrayOfStrings ?? [],
included: yamlConfig["included"].arrayOfStrings ?? [],
excluded: yamlConfig["excluded"].arrayOfStrings ?? []
excluded: yamlConfig["excluded"].arrayOfStrings ?? [],
reporter: yamlConfig["reporter"].string ?? XcodeReporter.identifier
)
}

Expand Down
7 changes: 6 additions & 1 deletion Source/SwiftLintFramework/Linter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import SourceKittenFramework

public struct Linter {
private let file: File

private let rules: [Rule]
private let reporter: Reporter.Type

public var styleViolations: [StyleViolation] {
let regions = file.regions()
Expand All @@ -29,6 +29,10 @@ public struct Linter {
}
}

public func generateReport() -> String {
return reporter.generateReport(styleViolations)
}

public var ruleExamples: [RuleExample] {
return rules.flatMap { $0.example }
}
Expand All @@ -41,5 +45,6 @@ public struct Linter {
public init(file: File, configuration: Configuration = Configuration()!) {
self.file = file
rules = configuration.rules
reporter = configuration.reporterFromString
}
}
12 changes: 12 additions & 0 deletions Source/SwiftLintFramework/Reporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Reporter.swift
// SwiftLint
//
// Created by JP Simard on 9/19/15.
// Copyright © 2015 Realm. All rights reserved.
//

public protocol Reporter: CustomStringConvertible {
static var identifier: String { get }
static func generateReport(violations: [StyleViolation]) -> String
}
41 changes: 41 additions & 0 deletions Source/SwiftLintFramework/Reporters/CSVReporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// CSVReporter.swift
// SwiftLint
//
// Created by JP Simard on 9/19/15.
// Copyright © 2015 Realm. All rights reserved.
//

import Foundation

public struct CSVReporter: Reporter {
public static let identifier = "csv"

public var description: String {
return "Reports violations as a newline-separated string of comma-separated values (CSV)."
}

public static func generateReport(violations: [StyleViolation]) -> String {
let keys = [
"file",
"line",
"character",
"severity",
"type",
"reason"
]
return (keys + violations.flatMap(arrayForViolation)).joinWithSeparator(",")
}

private static func arrayForViolation(violation: StyleViolation) -> [String] {
let values: [AnyObject?] = [
violation.location.file,
violation.location.line,
violation.location.character,
violation.severity.rawValue,
violation.type.description,
violation.reason
]
return values.map({ $0?.description ?? "" })
}
}
33 changes: 33 additions & 0 deletions Source/SwiftLintFramework/Reporters/JSONReporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// JSONReporter.swift
// SwiftLint
//
// Created by JP Simard on 9/19/15.
// Copyright © 2015 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct JSONReporter: Reporter {
public static let identifier = "json"

public var description: String {
return "Reports violations as a JSON array."
}

public static func generateReport(violations: [StyleViolation]) -> String {
return toJSON(violations.map(dictionaryForViolation))
}

private static func dictionaryForViolation(violation: StyleViolation) -> NSDictionary {
return [
"file": violation.location.file ?? NSNull(),
"line": violation.location.line ?? NSNull(),
"character": violation.location.character ?? NSNull(),
"severity": violation.severity.rawValue,
"type": violation.type.description,
"reason": violation.reason ?? NSNull()
]
}
}
27 changes: 27 additions & 0 deletions Source/SwiftLintFramework/Reporters/XcodeReporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// XcodeReporter.swift
// SwiftLint
//
// Created by JP Simard on 9/19/15.
// Copyright © 2015 Realm. All rights reserved.
//

public struct XcodeReporter: Reporter {
public static let identifier = "xcode"

public var description: String {
return "Reports violations in the format Xcode uses to display in the IDE. (default)"
}

public static func generateReport(violations: [StyleViolation]) -> String {
return violations.map(generateForSingleViolation).joinWithSeparator("\n")
}

internal static func generateForSingleViolation(violation: StyleViolation) -> String {
// {full_path_to_file}{:line}{:character}: {error,warning}: {content}
return "\(violation.location): " +
"\(violation.severity.rawValue.lowercaseString): " +
"\(violation.type) Violation: " +
(violation.reason ?? "")
}
}
6 changes: 1 addition & 5 deletions Source/SwiftLintFramework/StyleViolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ public struct StyleViolation: CustomStringConvertible, Equatable {
public let location: Location
public let reason: String?
public var description: String {
// {full_path_to_file}{:line}{:character}: {error,warning}: {content}
return "\(location): " +
"\(severity.rawValue.lowercaseString): " +
"\(type) Violation (\(severity) Severity): " +
(reason ?? "")
return XcodeReporter.generateForSingleViolation(self)
}

public init(type: StyleViolationType, location: Location, reason: String? = nil) {
Expand Down
2 changes: 2 additions & 0 deletions Source/SwiftLintFrameworkTests/ConfigurationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class ConfigurationTests: XCTestCase {
XCTAssertEqual(config.disabledRules, [])
XCTAssertEqual(config.included, [])
XCTAssertEqual(config.excluded, [])
XCTAssertEqual(config.reporter, "xcode")
XCTAssertEqual(config.reporterFromString.identifier, "xcode")
}

func testDisabledRules() {
Expand Down
66 changes: 66 additions & 0 deletions Source/SwiftLintFrameworkTests/ReporterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// ReporterTests.swift
// SwiftLint
//
// Created by JP Simard on 9/19/15.
// Copyright © 2015 Realm. All rights reserved.
//

import SwiftLintFramework
import XCTest

class ReporterTests: XCTestCase {
func generateViolations() -> [StyleViolation] {
return [
StyleViolation(type: .Length,
location: Location(file: "filename", line: 1, character: 2),
severity: .Warning,
reason: "Violation Reason."),
StyleViolation(type: .Length,
location: Location(file: "filename", line: 1, character: 2),
severity: .Error,
reason: "Violation Reason.")
]
}

func testXcodeReporter() {
XCTAssertEqual(
XcodeReporter.generateReport(generateViolations()),
"filename:1:2: warning: Length Violation: Violation Reason.\n" +
"filename:1:2: error: Length Violation: Violation Reason."
)
}

func testJSONReporter() {
XCTAssertEqual(
JSONReporter.generateReport(generateViolations()),
"[\n" +
" {\n" +
" \"type\" : \"Length\",\n" +
" \"line\" : 1,\n" +
" \"reason\" : \"Violation Reason.\",\n" +
" \"file\" : \"filename\",\n" +
" \"character\" : 2,\n" +
" \"severity\" : \"Warning\"\n" +
" },\n" +
" {\n" +
" \"type\" : \"Length\",\n" +
" \"line\" : 1,\n" +
" \"reason\" : \"Violation Reason.\",\n" +
" \"file\" : \"filename\",\n" +
" \"character\" : 2,\n" +
" \"severity\" : \"Error\"\n" +
" }\n" +
"]"
)
}

func testCSVReporter() {
XCTAssertEqual(
CSVReporter.generateReport(generateViolations()),
"file,line,character,severity,type,reason," +
"filename,1,2,Warning,Length,Violation Reason.," +
"filename,1,2,Error,Length,Violation Reason."
)
}
}
2 changes: 1 addition & 1 deletion Source/swiftlint/LintCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct LintCommand: CommandType {
if let stdinString = stdinNSString as? String {
let file = File(contents: stdinString)
let linter = Linter(file: file, configuration: configuration)
print(linter.styleViolations.map({ $0.description }).joinWithSeparator("\n"))
print(linter.generateReport())
return .Success()
}
return .Failure(CommandantError<()>.CommandError())
Expand Down
Loading

0 comments on commit 3c090e1

Please sign in to comment.