Skip to content

Commit bb7a20f

Browse files
danieleformichellinicklockwood
authored andcommitted
[Feature] Add support for JSON output (nicklockwood#900)
1 parent 482d912 commit bb7a20f

7 files changed

Lines changed: 100 additions & 9 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ DerivedData
2222
.swiftpm
2323

2424
/Snapshots/Private
25+
26+
# AppCode
27+
.idea

Sources/Arguments.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ let commandLineArguments = [
674674
"lenient",
675675
"verbose",
676676
"quiet",
677+
"report",
677678
// Misc
678679
"help",
679680
"version",

Sources/CommandLine.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ func printHelp(as type: CLI.OutputType) {
198198
--lenient Suppress errors for unformatted code in --lint mode
199199
--verbose Display detailed formatting output and warnings/errors
200200
--quiet Disables non-critical output messages and warnings
201+
--report The changes report file
201202
202203
SwiftFormat has a number of rules that can be enabled or disabled. By default
203204
most rules are enabled. Use --rules to display all enabled/disabled rules.
@@ -289,6 +290,11 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
289290
print("warning: \(warning)", as: .warning)
290291
}
291292

293+
// JSON output
294+
let jsonReporter = args["report"].map {
295+
JSONReporter(outputURL: URL(fileURLWithPath: $0))
296+
}
297+
292298
// Show help
293299
if args["help"] != nil {
294300
printHelp(as: .content)
@@ -624,7 +630,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
624630
}
625631
}
626632
let output = try applyRules(input, options: options, lineRange: lineRange,
627-
verbose: verbose, lint: lint)
633+
verbose: verbose, lint: lint, jsonReporter: jsonReporter)
628634
if let outputURL = outputURL, !useStdout {
629635
if (try? String(contentsOf: outputURL)) != output, !dryrun {
630636
do {
@@ -692,7 +698,8 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
692698
verbose: verbose,
693699
dryrun: dryrun,
694700
lint: lint,
695-
cacheURL: cacheURL)
701+
cacheURL: cacheURL,
702+
jsonReporter: jsonReporter)
696703
errors += _errors
697704
})
698705

@@ -704,6 +711,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
704711
print("warning: No eligible files found at \(inputPaths).", as: .warning)
705712
}
706713
print("SwiftFormat completed in \(time).", as: .success)
714+
try jsonReporter?.write()
707715
return printResult(dryrun, lint, lenient, outputFlags)
708716
} catch {
709717
_ = printWarnings(errors)
@@ -777,7 +785,7 @@ func computeHash(_ source: String) -> String {
777785
}
778786

779787
func applyRules(_ source: String, options: Options, lineRange: ClosedRange<Int>?,
780-
verbose: Bool, lint: Bool) throws -> String
788+
verbose: Bool, lint: Bool, jsonReporter: JSONReporter?) throws -> String
781789
{
782790
// Parse source
783791
var tokens = tokenize(source)
@@ -802,6 +810,7 @@ func applyRules(_ source: String, options: Options, lineRange: ClosedRange<Int>?
802810
let updatedSource = sourceCode(for: tokens)
803811
if lint, updatedSource != source {
804812
changes.forEach { print($0.description, as: .warning) }
813+
jsonReporter?.report(changes)
805814
}
806815
if verbose {
807816
let rulesApplied = changes.reduce(into: Set<String>()) {
@@ -827,7 +836,8 @@ func processInput(_ inputURLs: [URL],
827836
verbose: Bool,
828837
dryrun: Bool,
829838
lint: Bool,
830-
cacheURL: URL?) -> (OutputFlags, [Error])
839+
cacheURL: URL?,
840+
jsonReporter: JSONReporter?) -> (OutputFlags, [Error])
831841
{
832842
// Load cache
833843
let cacheDirectory = cacheURL?.deletingLastPathComponent().absoluteURL
@@ -914,7 +924,7 @@ func processInput(_ inputURLs: [URL],
914924
}
915925
} else {
916926
output = try applyRules(input, options: options, lineRange: lineRange,
917-
verbose: verbose, lint: lint)
927+
verbose: verbose, lint: lint, jsonReporter: jsonReporter)
918928
if output != input {
919929
sourceHash = nil
920930
}

Sources/Formatter.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,11 @@ public class Formatter: NSObject {
171171
public let rule: FormatRule
172172
public let filePath: String?
173173

174+
public var help: String {
175+
return stripMarkdown(rule.help).replacingOccurrences(of: "\n", with: " ")
176+
}
177+
174178
public var description: String {
175-
let help = stripMarkdown(rule.help).replacingOccurrences(of: "\n", with: " ")
176179
return "\(filePath ?? ""):\(line):1: warning: (\(rule.name)) \(help)"
177180
}
178181
}

Sources/JSONReporter.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// CommandLine.swift
3+
// SwiftFormat
4+
//
5+
// Created by Daniele Formichelli on 09/04/2021.
6+
// Copyright 2021 Nick Lockwood
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/nicklockwood/SwiftFormat
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy
14+
// of this software and associated documentation files (the "Software"), to deal
15+
// in the Software without restriction, including without limitation the rights
16+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
// copies of the Software, and to permit persons to whom the Software is
18+
// furnished to do so, subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in all
21+
// copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
// SOFTWARE.
30+
//
31+
32+
import Foundation
33+
34+
class JSONReporter {
35+
let outputURL: URL
36+
var changes: [Formatter.Change] = []
37+
38+
init(outputURL: URL) {
39+
self.outputURL = outputURL
40+
}
41+
42+
func report(_ changes: [Formatter.Change]) {
43+
self.changes.append(contentsOf: changes)
44+
}
45+
46+
func write() throws {
47+
try JSONEncoder().encode(changes).write(to: outputURL)
48+
}
49+
}
50+
51+
extension Formatter.Change: Encodable {
52+
enum CodingKeys: String, CodingKey {
53+
case file
54+
case line
55+
case reason
56+
case ruleID = "rule_id"
57+
}
58+
59+
public func encode(to encoder: Encoder) throws {
60+
var container = encoder.container(keyedBy: CodingKeys.self)
61+
if let filePath = self.filePath {
62+
try container.encode(filePath, forKey: .file)
63+
}
64+
try container.encode(line, forKey: .line)
65+
try container.encode(help, forKey: .reason)
66+
try container.encode(rule.name, forKey: .ruleID)
67+
}
68+
}

SwiftFormat.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
90C4B6EB1DA4B059009EB000 /* SwiftFormat.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 90C4B6DD1DA4B059009EB000 /* SwiftFormat.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
8888
90F16AF81DA5EB4600EB4EA1 /* FormatFileCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90F16AF71DA5EB4600EB4EA1 /* FormatFileCommand.swift */; };
8989
90F16AFB1DA5ED9A00EB4EA1 /* CommandErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90F16AFA1DA5ED9A00EB4EA1 /* CommandErrors.swift */; };
90+
A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DF48242620E03600F45A5F /* JSONReporter.swift */; };
91+
A3DF48262620E03600F45A5F /* JSONReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DF48242620E03600F45A5F /* JSONReporter.swift */; };
9092
B9C4F55C2387FA3E0088DBEE /* SupportedContentUTIs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C4F55B2387FA3E0088DBEE /* SupportedContentUTIs.swift */; };
9193
E4083191202C049200CAF11D /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; };
9294
E41CB5BF2025761D00C1BEDE /* UserSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41CB5BE2025761D00C1BEDE /* UserSelection.swift */; };
@@ -223,6 +225,7 @@
223225
90CB44D81DA4B56500F86C22 /* SwiftFormatter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftFormatter.entitlements; sourceTree = "<group>"; };
224226
90F16AF71DA5EB4600EB4EA1 /* FormatFileCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatFileCommand.swift; sourceTree = "<group>"; };
225227
90F16AFA1DA5ED9A00EB4EA1 /* CommandErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandErrors.swift; sourceTree = "<group>"; };
228+
A3DF48242620E03600F45A5F /* JSONReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONReporter.swift; sourceTree = "<group>"; };
226229
B9C4F55B2387FA3E0088DBEE /* SupportedContentUTIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedContentUTIs.swift; sourceTree = "<group>"; };
227230
E41CB5BE2025761D00C1BEDE /* UserSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelection.swift; sourceTree = "<group>"; };
228231
E41CB5C22026CACD00C1BEDE /* ListSelectionTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListSelectionTableCellView.swift; sourceTree = "<group>"; };
@@ -326,6 +329,7 @@
326329
01A0EAA61D5DB4CF00A0A8E3 /* Sources */ = {
327330
isa = PBXGroup;
328331
children = (
332+
A3DF48242620E03600F45A5F /* JSONReporter.swift */,
329333
01F17E811E25870700DCD359 /* CommandLine.swift */,
330334
E4E4D3C82033F17C000D7CB1 /* EnumAssociable.swift */,
331335
01B3987C1D763493009ADE61 /* Formatter.swift */,
@@ -771,6 +775,7 @@
771775
01ACAE05220CD90F003F3CCF /* Examples.swift in Sources */,
772776
01D3B28624E9C9C700888DE0 /* FormattingHelpers.swift in Sources */,
773777
E4E4D3C92033F17C000D7CB1 /* EnumAssociable.swift in Sources */,
778+
A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */,
774779
01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */,
775780
01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */,
776781
01B3987D1D763493009ADE61 /* Formatter.swift in Sources */,
@@ -824,6 +829,7 @@
824829
01045A92211988F100D2BE3D /* Inference.swift in Sources */,
825830
01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */,
826831
E4FABAD6202FEF060065716E /* OptionDescriptor.swift in Sources */,
832+
A3DF48262620E03600F45A5F /* JSONReporter.swift in Sources */,
827833
01A8320724EC7F7600A9D0EB /* FormattingHelpers.swift in Sources */,
828834
01F17E831E25870700DCD359 /* CommandLine.swift in Sources */,
829835
01ACAE06220CD914003F3CCF /* Examples.swift in Sources */,

Tests/SwiftFormatTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class SwiftFormatTests: XCTestCase {
6767
return { files.append(inputURL) }
6868
}
6969
XCTAssertEqual(errors.count, 0)
70-
XCTAssertEqual(files.count, 55)
70+
XCTAssertEqual(files.count, 56)
7171
}
7272

7373
func testInputFilesMatchOutputFilesForSameOutput() {
@@ -78,7 +78,7 @@ class SwiftFormatTests: XCTestCase {
7878
return { files.append(inputURL) }
7979
}
8080
XCTAssertEqual(errors.count, 0)
81-
XCTAssertEqual(files.count, 55)
81+
XCTAssertEqual(files.count, 56)
8282
}
8383

8484
func testInputFileNotEnumeratedWhenExcluded() {
@@ -93,7 +93,7 @@ class SwiftFormatTests: XCTestCase {
9393
return { files.append(inputURL) }
9494
}
9595
XCTAssertEqual(errors.count, 0)
96-
XCTAssertEqual(files.count, 33)
96+
XCTAssertEqual(files.count, 34)
9797
}
9898

9999
// MARK: format function

0 commit comments

Comments
 (0)