Skip to content

Commit 89761e4

Browse files
committed
Improve --report logic
1 parent bb7a20f commit 89761e4

2 files changed

Lines changed: 53 additions & 23 deletions

File tree

Sources/CommandLine.swift

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ func printHelp(as type: CLI.OutputType) {
194194
--minversion The minimum SwiftFormat version to be used for these files
195195
--cache Path to cache file, or "clear" or "ignore" the default cache
196196
--dryrun Run in "dry" mode (without actually changing any files)
197-
--lint Like --dryrun, but returns an error if formatting is needed
197+
--lint Return an error for unformatted input, and list violations
198+
--report Path to a file where --lint output should be written
198199
--lenient Suppress errors for unformatted code in --lint mode
199200
--verbose Display detailed formatting output and warnings/errors
200201
--quiet Disables non-critical output messages and warnings
201-
--report The changes report file
202202
203203
SwiftFormat has a number of rules that can be enabled or disabled. By default
204204
most rules are enabled. Use --rules to display all enabled/disabled rules.
@@ -290,8 +290,8 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
290290
print("warning: \(warning)", as: .warning)
291291
}
292292

293-
// JSON output
294-
let jsonReporter = args["report"].map {
293+
// Report output
294+
let reporter = args["report"].map {
295295
JSONReporter(outputURL: URL(fileURLWithPath: $0))
296296
}
297297

@@ -476,6 +476,9 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
476476
useStdout = true
477477
return URL(string: arg)
478478
}
479+
if args["lint"] != nil {
480+
print("warning: --output argument is unused when running in --lint mode", as: .warning)
481+
}
479482
return try parsePath(arg, for: "--output", in: directory)
480483
}
481484

@@ -630,7 +633,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
630633
}
631634
}
632635
let output = try applyRules(input, options: options, lineRange: lineRange,
633-
verbose: verbose, lint: lint, jsonReporter: jsonReporter)
636+
verbose: verbose, lint: lint, reporter: reporter)
634637
if let outputURL = outputURL, !useStdout {
635638
if (try? String(contentsOf: outputURL)) != output, !dryrun {
636639
do {
@@ -699,7 +702,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
699702
dryrun: dryrun,
700703
lint: lint,
701704
cacheURL: cacheURL,
702-
jsonReporter: jsonReporter)
705+
reporter: reporter)
703706
errors += _errors
704707
})
705708

@@ -710,8 +713,11 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
710713
let inputPaths = inputURLs.map { $0.path }.joined(separator: ", ")
711714
print("warning: No eligible files found at \(inputPaths).", as: .warning)
712715
}
716+
if let reporter = reporter {
717+
print("Writing report file to \(reporter.outputURL.path)")
718+
try reporter.write()
719+
}
713720
print("SwiftFormat completed in \(time).", as: .success)
714-
try jsonReporter?.write()
715721
return printResult(dryrun, lint, lenient, outputFlags)
716722
} catch {
717723
_ = printWarnings(errors)
@@ -785,7 +791,7 @@ func computeHash(_ source: String) -> String {
785791
}
786792

787793
func applyRules(_ source: String, options: Options, lineRange: ClosedRange<Int>?,
788-
verbose: Bool, lint: Bool, jsonReporter: JSONReporter?) throws -> String
794+
verbose: Bool, lint: Bool, reporter: JSONReporter?) throws -> String
789795
{
790796
// Parse source
791797
var tokens = tokenize(source)
@@ -803,14 +809,17 @@ func applyRules(_ source: String, options: Options, lineRange: ClosedRange<Int>?
803809
let formatOptions = options.formatOptions ?? .default
804810
var changes = [Formatter.Change]()
805811
let range = lineRange.map { tokenRange(forLineRange: $0, in: tokens) }
806-
(tokens, changes) = try applyRules(rules, to: tokens, with: formatOptions,
807-
trackChanges: lint || verbose, range: range)
812+
(tokens, changes) = try applyRules(
813+
rules, to: tokens, with: formatOptions,
814+
trackChanges: lint || verbose || reporter != nil,
815+
range: range
816+
)
808817

809818
// Display info
810819
let updatedSource = sourceCode(for: tokens)
811820
if lint, updatedSource != source {
812821
changes.forEach { print($0.description, as: .warning) }
813-
jsonReporter?.report(changes)
822+
reporter?.report(changes)
814823
}
815824
if verbose {
816825
let rulesApplied = changes.reduce(into: Set<String>()) {
@@ -837,7 +846,7 @@ func processInput(_ inputURLs: [URL],
837846
dryrun: Bool,
838847
lint: Bool,
839848
cacheURL: URL?,
840-
jsonReporter: JSONReporter?) -> (OutputFlags, [Error])
849+
reporter: JSONReporter?) -> (OutputFlags, [Error])
841850
{
842851
// Load cache
843852
let cacheDirectory = cacheURL?.deletingLastPathComponent().absoluteURL
@@ -924,7 +933,7 @@ func processInput(_ inputURLs: [URL],
924933
}
925934
} else {
926935
output = try applyRules(input, options: options, lineRange: lineRange,
927-
verbose: verbose, lint: lint, jsonReporter: jsonReporter)
936+
verbose: verbose, lint: lint, reporter: reporter)
928937
if output != input {
929938
sourceHash = nil
930939
}

Sources/JSONReporter.swift

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,46 @@ class JSONReporter {
4444
}
4545

4646
func write() throws {
47-
try JSONEncoder().encode(changes).write(to: outputURL)
47+
let encoder = JSONEncoder()
48+
encoder.keyEncodingStrategy = .convertToSnakeCase
49+
encoder.outputFormatting = .prettyPrinted
50+
if #available(macOS 10.13, *) {
51+
encoder.outputFormatting.insert(.sortedKeys)
52+
}
53+
let stripSlashes: Bool
54+
#if swift(>=5.2)
55+
if #available(macOS 10.15, *) {
56+
stripSlashes = false
57+
encoder.outputFormatting.insert(.withoutEscapingSlashes)
58+
} else {
59+
stripSlashes = true
60+
}
61+
#else
62+
stripSlashes = true
63+
#endif
64+
var data = try encoder.encode(changes.map(ReportItem.init))
65+
if stripSlashes, let string = String(data: data, encoding: .utf8) {
66+
data = string.replacingOccurrences(of: "\\/", with: "/").data(using: .utf8) ?? data
67+
}
68+
try data.write(to: outputURL, options: .atomic)
4869
}
4970
}
5071

51-
extension Formatter.Change: Encodable {
72+
private struct ReportItem: Encodable {
73+
var change: Formatter.Change
74+
5275
enum CodingKeys: String, CodingKey {
5376
case file
5477
case line
5578
case reason
56-
case ruleID = "rule_id"
79+
case ruleID
5780
}
5881

59-
public func encode(to encoder: Encoder) throws {
82+
func encode(to encoder: Encoder) throws {
6083
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)
84+
try container.encodeIfPresent(change.filePath, forKey: .file)
85+
try container.encode(change.line, forKey: .line)
86+
try container.encode(change.help, forKey: .reason)
87+
try container.encode(change.rule.name, forKey: .ruleID)
6788
}
6889
}

0 commit comments

Comments
 (0)