Skip to content

Commit

Permalink
Add XML reporter (#1682)
Browse files Browse the repository at this point in the history
  • Loading branch information
saeed-rz authored and nicklockwood committed May 18, 2024
1 parent 404e76e commit 45d8c2c
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 6 deletions.
5 changes: 2 additions & 3 deletions Sources/Reporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,12 @@ enum Reporters {
static let all: [Reporter.Type] = [
JSONReporter.self,
GithubActionsLogReporter.self,
XMLReporter.self,
]

static var help: String {
let names = all.map { "\"\($0.name)\"" }
return names.dropLast().joined(separator: ", ") + (names.last.map {
" or \($0)"
} ?? "")
return names.joined(separator: ", ")
}

static func reporter(named: String, environment: [String: String]) -> Reporter? {
Expand Down
85 changes: 85 additions & 0 deletions Sources/XMLReporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// XMLReporter.swift
// SwiftFormat
//
// Created by Saeid Rezaei on 13/04/2024.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
//
// Distributed under the permissive MIT license
// Get the latest version from here:
//
// https://github.com/nicklockwood/SwiftFormat
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
/// Reports changes as XML conforming to the Checkstyle specification, as defined here:
/// https://www.jetbrains.com/help/teamcity/xml-report-processing.html
import Foundation

final class XMLReporter: Reporter {
static var name = "xml"
static var fileExtension: String? = "xml"

private var changes: [Formatter.Change] = []

init(environment _: [String: String]) {}

func report(_ changes: [Formatter.Change]) {
self.changes.append(contentsOf: changes)
}

func write() throws -> Data? {
let fileChanges = Dictionary(grouping: changes, by: { $0.filePath ?? "<nopath>" })
let report = [
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<checkstyle version=\"4.3\">",
fileChanges
.sorted(by: { $0.key < $1.key })
.map(generateChangeForFile).joined(),
"\n</checkstyle>",
].joined()

return Data(report.utf8)
}

// MARK: - Private

private func generateChangeForFile(_ file: String, fileChanges: [Formatter.Change]) -> String {
[
"\n\t<file name=\"", file, "\">\n",
fileChanges.map(generateChange).joined(),
"\t</file>",
].joined()
}

private func generateChange(_ change: Formatter.Change) -> String {
let line = change.line
let col = 0
let severity = "warning"
let reason = change.help
let rule = change.rule.name
return [
"\t\t<error line=\"\(line)\" ",
"column=\"\(col)\" ",
"severity=\"", severity, "\" ",
"message=\"", reason, "\" ",
"source=\"\(rule)\"/>\n",
].joined()
}
}
10 changes: 10 additions & 0 deletions SwiftFormat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@
A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DF48242620E03600F45A5F /* JSONReporter.swift */; };
A3DF48262620E03600F45A5F /* JSONReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DF48242620E03600F45A5F /* JSONReporter.swift */; };
B9C4F55C2387FA3E0088DBEE /* SupportedContentUTIs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C4F55B2387FA3E0088DBEE /* SupportedContentUTIs.swift */; };
C2FFD1822BD13C9E00774F55 /* XMLReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FFD1812BD13C9E00774F55 /* XMLReporter.swift */; };
C2FFD1832BD13C9E00774F55 /* XMLReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FFD1812BD13C9E00774F55 /* XMLReporter.swift */; };
C2FFD1842BD13C9E00774F55 /* XMLReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FFD1812BD13C9E00774F55 /* XMLReporter.swift */; };
C2FFD1852BD13C9E00774F55 /* XMLReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FFD1812BD13C9E00774F55 /* XMLReporter.swift */; };
D52F6A642A82E04600FE1448 /* GitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52F6A632A82E04600FE1448 /* GitHelpers.swift */; };
D52F6A652A82E04600FE1448 /* GitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52F6A632A82E04600FE1448 /* GitHelpers.swift */; };
D52F6A662A82E04600FE1448 /* GitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52F6A632A82E04600FE1448 /* GitHelpers.swift */; };
Expand Down Expand Up @@ -254,6 +258,7 @@
90F16AFA1DA5ED9A00EB4EA1 /* CommandErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandErrors.swift; sourceTree = "<group>"; };
A3DF48242620E03600F45A5F /* JSONReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONReporter.swift; sourceTree = "<group>"; };
B9C4F55B2387FA3E0088DBEE /* SupportedContentUTIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedContentUTIs.swift; sourceTree = "<group>"; };
C2FFD1812BD13C9E00774F55 /* XMLReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLReporter.swift; sourceTree = "<group>"; };
D52F6A632A82E04600FE1448 /* GitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHelpers.swift; sourceTree = "<group>"; };
D52F6A682A82E0DD00FE1448 /* ShellHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellHelpers.swift; sourceTree = "<group>"; };
DD9AD39C2999FCC8001C2C0E /* GithubActionsLogReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GithubActionsLogReporter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -382,6 +387,7 @@
01BBD85821DAA2A000457380 /* Globs.swift */,
D52F6A632A82E04600FE1448 /* GitHelpers.swift */,
D52F6A682A82E0DD00FE1448 /* ShellHelpers.swift */,
C2FFD1812BD13C9E00774F55 /* XMLReporter.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -809,6 +815,7 @@
A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */,
01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */,
01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */,
C2FFD1822BD13C9E00774F55 /* XMLReporter.swift in Sources */,
2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */,
01B3987D1D763493009ADE61 /* Formatter.swift in Sources */,
01F17E821E25870700DCD359 /* CommandLine.swift in Sources */,
Expand Down Expand Up @@ -873,6 +880,7 @@
01F17E831E25870700DCD359 /* CommandLine.swift in Sources */,
015243E22B04B0A600F65221 /* Singularize.swift in Sources */,
01ACAE06220CD914003F3CCF /* Examples.swift in Sources */,
C2FFD1832BD13C9E00774F55 /* XMLReporter.swift in Sources */,
01A0EACD1D5DB5F500A0A8E3 /* main.swift in Sources */,
DD9AD3A42999FCC8001C2C0E /* Reporter.swift in Sources */,
01045A9A2119979400D2BE3D /* Arguments.swift in Sources */,
Expand All @@ -895,6 +903,7 @@
D52F6A6B2A82E0DD00FE1448 /* ShellHelpers.swift in Sources */,
E4872112201D3B860014845E /* Rules.swift in Sources */,
E4962DE0203F3CD500A02013 /* OptionsStore.swift in Sources */,
C2FFD1842BD13C9E00774F55 /* XMLReporter.swift in Sources */,
01ACAE07220CD915003F3CCF /* Examples.swift in Sources */,
E4872113201D3B890014845E /* Formatter.swift in Sources */,
E4E4D3CB2033F17C000D7CB1 /* EnumAssociable.swift in Sources */,
Expand Down Expand Up @@ -927,6 +936,7 @@
D52F6A6C2A82E0DD00FE1448 /* ShellHelpers.swift in Sources */,
9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */,
90F16AFB1DA5ED9A00EB4EA1 /* CommandErrors.swift in Sources */,
C2FFD1852BD13C9E00774F55 /* XMLReporter.swift in Sources */,
01A8320924EC7F7800A9D0EB /* FormattingHelpers.swift in Sources */,
018541CF1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift in Sources */,
E4962DE1203F3CD500A02013 /* OptionsStore.swift in Sources */,
Expand Down
44 changes: 44 additions & 0 deletions Tests/CommandLineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -558,4 +558,48 @@ class CommandLineTests: XCTestCase {
], in: "")
}
}

func testXMLReporterEndToEnd() throws {
try withTmpFiles([
"foo.swift": "func foo() {\n}\n",
]) { url in
CLI.print = { message, type in
switch type {
case .raw:
XCTAssert(message.contains("<error line=\"1\" column=\"0\" severity=\"warning\""))
case .error, .warning:
break
case .info, .success:
break
case .content:
XCTFail()
}
}
_ = processArguments([
"",
"--lint",
"--reporter",
"xml",
url.path,
], in: "")
}
}

func testXMLReporterInferredFromURL() throws {
let outputURL = try createTmpFile("report.xml", contents: "")
try withTmpFiles([
"foo.swift": "func foo() {\n}\n",
]) { url in
CLI.print = { _, _ in }
_ = processArguments([
"",
"--lint",
"--report",
outputURL.path,
url.path,
], in: "")
}
let ouput = try String(contentsOf: outputURL)
XCTAssert(ouput.contains("<error line=\"1\" column=\"0\" severity=\"warning\""))
}
}
6 changes: 3 additions & 3 deletions Tests/SwiftFormatTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class SwiftFormatTests: XCTestCase {
return { files.append(inputURL) }
}
XCTAssertEqual(errors.count, 0)
XCTAssertEqual(files.count, 72)
XCTAssertEqual(files.count, 73)
}

func testInputFilesMatchOutputFilesForSameOutput() {
Expand All @@ -78,7 +78,7 @@ class SwiftFormatTests: XCTestCase {
return { files.append(inputURL) }
}
XCTAssertEqual(errors.count, 0)
XCTAssertEqual(files.count, 72)
XCTAssertEqual(files.count, 73)
}

func testInputFileNotEnumeratedWhenExcluded() {
Expand All @@ -93,7 +93,7 @@ class SwiftFormatTests: XCTestCase {
return { files.append(inputURL) }
}
XCTAssertEqual(errors.count, 0)
XCTAssertEqual(files.count, 45)
XCTAssertEqual(files.count, 46)
}

// MARK: format function
Expand Down

0 comments on commit 45d8c2c

Please sign in to comment.