Skip to content

Commit

Permalink
added custom reporters: xcode (default), json, csv. fixes #42
Browse files Browse the repository at this point in the history
  • Loading branch information
jpsim committed Sep 19, 2015
1 parent d2496d8 commit ee79209
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 7 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")
}

private 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 ?? "")
}
}
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/StyleViolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public struct StyleViolation: CustomStringConvertible, Equatable {
// {full_path_to_file}{:line}{:character}: {error,warning}: {content}
return "\(location): " +
"\(severity.rawValue.lowercaseString): " +
"\(type) Violation (\(severity) Severity): " +
"\(type) Violation: " +
(reason ?? "")
}

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
32 changes: 30 additions & 2 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,19 @@
E5A167C91B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */; };
E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E809EDA01B8A71DF00399043 /* Configuration.swift */; };
E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E809EDA21B8A73FB00399043 /* ConfigurationTests.swift */; };
E80E018D1B92C0F60078EB70 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E018C1B92C0F60078EB70 /* Command.swift */; settings = {ASSET_TAGS = (); }; };
E80E018F1B92C1350078EB70 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E018E1B92C1350078EB70 /* Region.swift */; settings = {ASSET_TAGS = (); }; };
E80E018D1B92C0F60078EB70 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E018C1B92C0F60078EB70 /* Command.swift */; };
E80E018F1B92C1350078EB70 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E018E1B92C1350078EB70 /* Region.swift */; };
E812249A1B04F85B001783D2 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81224991B04F85B001783D2 /* TestHelpers.swift */; };
E812249C1B04FADC001783D2 /* Linter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E812249B1B04FADC001783D2 /* Linter.swift */; };
E832F10B1B17E2F5003F265F /* NSFileManager+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832F10A1B17E2F5003F265F /* NSFileManager+SwiftLint.swift */; };
E832F10D1B17E725003F265F /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832F10C1B17E725003F265F /* IntegrationTests.swift */; };
E83A0B351A5D382B0041A60A /* VersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E83A0B341A5D382B0041A60A /* VersionCommand.swift */; };
E861519B1B0573B900C54AC0 /* LintCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E861519A1B0573B900C54AC0 /* LintCommand.swift */; };
E86396C21BADAAE5002C9E88 /* Reporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86396C11BADAAE5002C9E88 /* Reporter.swift */; };
E86396C51BADAC15002C9E88 /* XcodeReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86396C41BADAC15002C9E88 /* XcodeReporter.swift */; };
E86396C71BADAFE6002C9E88 /* ReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86396C61BADAFE6002C9E88 /* ReporterTests.swift */; };
E86396C91BADB2B9002C9E88 /* JSONReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86396C81BADB2B9002C9E88 /* JSONReporter.swift */; };
E86396CB1BADB519002C9E88 /* CSVReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86396CA1BADB519002C9E88 /* CSVReporter.swift */; };
E86847371A587AF40043DC65 /* SwiftXPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E86847361A587AF40043DC65 /* SwiftXPC.framework */; };
E86847381A587B0A0043DC65 /* SwiftXPC.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = E86847361A587AF40043DC65 /* SwiftXPC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E868473C1A587C6E0043DC65 /* sourcekitd.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E868473B1A587C6E0043DC65 /* sourcekitd.framework */; };
Expand Down Expand Up @@ -161,6 +166,11 @@
E832F10C1B17E725003F265F /* IntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
E83A0B341A5D382B0041A60A /* VersionCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionCommand.swift; sourceTree = "<group>"; };
E861519A1B0573B900C54AC0 /* LintCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintCommand.swift; sourceTree = "<group>"; };
E86396C11BADAAE5002C9E88 /* Reporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reporter.swift; sourceTree = "<group>"; };
E86396C41BADAC15002C9E88 /* XcodeReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeReporter.swift; sourceTree = "<group>"; };
E86396C61BADAFE6002C9E88 /* ReporterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReporterTests.swift; sourceTree = "<group>"; };
E86396C81BADB2B9002C9E88 /* JSONReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONReporter.swift; sourceTree = "<group>"; };
E86396CA1BADB519002C9E88 /* CSVReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReporter.swift; sourceTree = "<group>"; };
E86847361A587AF40043DC65 /* SwiftXPC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftXPC.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E868473B1A587C6E0043DC65 /* sourcekitd.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = sourcekitd.framework; path = Toolchains/XcodeDefault.xctoolchain/usr/lib/sourcekitd.framework; sourceTree = DEVELOPER_DIR; };
E876BFBD1B07828500114ED5 /* SourceKittenFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SourceKittenFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -362,6 +372,8 @@
E88DEA6E1B09843F00A66CB0 /* Location.swift */,
E832F10A1B17E2F5003F265F /* NSFileManager+SwiftLint.swift */,
E80E018E1B92C1350078EB70 /* Region.swift */,
E86396C11BADAAE5002C9E88 /* Reporter.swift */,
E86396C31BADAC0D002C9E88 /* Reporters */,
E88DEA761B098D0C00A66CB0 /* Rule.swift */,
83D71E261B131EB5000395DE /* RuleExample.swift */,
E88DEA781B098D4400A66CB0 /* RuleParameter.swift */,
Expand Down Expand Up @@ -398,6 +410,7 @@
E8BB8F991B17DDB200199606 /* ASTRuleTests.swift */,
E809EDA21B8A73FB00399043 /* ConfigurationTests.swift */,
E832F10C1B17E725003F265F /* IntegrationTests.swift */,
E86396C61BADAFE6002C9E88 /* ReporterTests.swift */,
E8BB8F9B1B17DE3B00199606 /* StringRuleTests.swift */,
E81224991B04F85B001783D2 /* TestHelpers.swift */,
D0D1212219E878CC005E4BAA /* Configuration */,
Expand All @@ -415,6 +428,16 @@
name = "Supporting Files";
sourceTree = "<group>";
};
E86396C31BADAC0D002C9E88 /* Reporters */ = {
isa = PBXGroup;
children = (
E86396CA1BADB519002C9E88 /* CSVReporter.swift */,
E86396C81BADB2B9002C9E88 /* JSONReporter.swift */,
E86396C41BADAC15002C9E88 /* XcodeReporter.swift */,
);
path = Reporters;
sourceTree = "<group>";
};
E88DEA7A1B098D7300A66CB0 /* Rules */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -607,6 +630,7 @@
E57B23C11B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift in Sources */,
E88DEA841B0990F500A66CB0 /* ColonRule.swift in Sources */,
E88DEA791B098D4400A66CB0 /* RuleParameter.swift in Sources */,
E86396CB1BADB519002C9E88 /* CSVReporter.swift in Sources */,
E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */,
E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */,
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */,
Expand All @@ -616,7 +640,9 @@
E88DEA8A1B0992B300A66CB0 /* FileLengthRule.swift in Sources */,
E88DEA751B09852000A66CB0 /* File+SwiftLint.swift in Sources */,
65454F471B14D76500319A6C /* ControlStatementRule.swift in Sources */,
E86396C51BADAC15002C9E88 /* XcodeReporter.swift in Sources */,
E88DEA8E1B0999CD00A66CB0 /* TypeBodyLengthRule.swift in Sources */,
E86396C91BADB2B9002C9E88 /* JSONReporter.swift in Sources */,
E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */,
E88DEA881B09924C00A66CB0 /* TrailingNewlineRule.swift in Sources */,
E88DEA771B098D0C00A66CB0 /* Rule.swift in Sources */,
Expand All @@ -631,6 +657,7 @@
E88DEA6B1B0983FE00A66CB0 /* StyleViolation.swift in Sources */,
E832F10B1B17E2F5003F265F /* NSFileManager+SwiftLint.swift in Sources */,
E5A167C91B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift in Sources */,
E86396C21BADAAE5002C9E88 /* Reporter.swift in Sources */,
E88DEA7E1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -641,6 +668,7 @@
files = (
E832F10D1B17E725003F265F /* IntegrationTests.swift in Sources */,
E812249A1B04F85B001783D2 /* TestHelpers.swift in Sources */,
E86396C71BADAFE6002C9E88 /* ReporterTests.swift in Sources */,
E8BB8F9C1B17DE3B00199606 /* StringRuleTests.swift in Sources */,
E8BB8F9A1B17DDB200199606 /* ASTRuleTests.swift in Sources */,
E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */,
Expand Down

0 comments on commit ee79209

Please sign in to comment.