Skip to content

Commit f3e5cc5

Browse files
committed
Remove the swift-tools-support-core dependency from swift-format.
This was only being used for the diagnostics engine, which is easy enough to roll by hand and it's one less dependency to keep in sync with the rest of the toolchain. In the future, we may want an output mode that uses the new prettier diagnostic printer from swift-syntax.
1 parent e0d014c commit f3e5cc5

File tree

8 files changed

+254
-173
lines changed

8 files changed

+254
-173
lines changed

Package.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ let package = Package(
143143
.product(name: "ArgumentParser", package: "swift-argument-parser"),
144144
.product(name: "SwiftSyntax", package: "swift-syntax"),
145145
.product(name: "SwiftParser", package: "swift-syntax"),
146-
.product(name: "TSCBasic", package: "swift-tools-support-core"),
147146
]
148147
),
149148

@@ -223,15 +222,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
223222
url: "https://github.com/apple/swift-syntax.git",
224223
branch: "main"
225224
),
226-
.package(
227-
url: "https://github.com/apple/swift-tools-support-core.git",
228-
exact: Version("0.4.0")
229-
),
230225
]
231226
} else {
232227
package.dependencies += [
233228
.package(path: "../swift-argument-parser"),
234229
.package(path: "../swift-syntax"),
235-
.package(path: "../swift-tools-support-core"),
236230
]
237231
}

Sources/swift-format/Frontend/FormatFrontend.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class FormatFrontend: Frontend {
4040
return
4141
}
4242

43-
let diagnosticHandler: (Diagnostic, SourceLocation) -> () = { (diagnostic, location) in
43+
let diagnosticHandler: (SwiftDiagnostics.Diagnostic, SourceLocation) -> () = {
44+
(diagnostic, location) in
4445
guard !self.lintFormatOptions.ignoreUnparsableFiles else {
4546
// No diagnostics should be emitted in this mode.
4647
return

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class Frontend {
5757
final let diagnosticPrinter: StderrDiagnosticPrinter
5858

5959
/// The diagnostic engine to which warnings and errors will be emitted.
60-
final let diagnosticsEngine: UnifiedDiagnosticsEngine
60+
final let diagnosticsEngine: DiagnosticsEngine
6161

6262
/// Options that apply during formatting or linting.
6363
final let lintFormatOptions: LintFormatOptions
@@ -83,7 +83,7 @@ class Frontend {
8383
self.diagnosticPrinter = StderrDiagnosticPrinter(
8484
colorMode: lintFormatOptions.colorDiagnostics.map { $0 ? .on : .off } ?? .auto)
8585
self.diagnosticsEngine =
86-
UnifiedDiagnosticsEngine(diagnosticsHandlers: [diagnosticPrinter.printDiagnostic])
86+
DiagnosticsEngine(diagnosticsHandlers: [diagnosticPrinter.printDiagnostic])
8787
}
8888

8989
/// Runs the linter or formatter over the inputs.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftFormatCore
14+
import SwiftSyntax
15+
16+
/// Diagnostic data that retains the separation of a finding category (if present) from the rest of
17+
/// the message, allowing diagnostic printers that want to print those values separately to do so.
18+
struct Diagnostic {
19+
/// The severity of the diagnostic.
20+
enum Severity {
21+
case note
22+
case warning
23+
case error
24+
}
25+
26+
/// Represents the location of a diagnostic.
27+
struct Location {
28+
/// The file path associated with the diagnostic.
29+
var file: String
30+
31+
/// The 1-based line number where the diagnostic occurred.
32+
var line: Int
33+
34+
/// The 1-based column number where the diagnostic occurred.
35+
var column: Int
36+
37+
/// Creates a new diagnostic location from the given source location.
38+
init(_ sourceLocation: SourceLocation) {
39+
self.file = sourceLocation.file!
40+
self.line = sourceLocation.line!
41+
self.column = sourceLocation.column!
42+
}
43+
44+
/// Creates a new diagnostic location with the given finding location.
45+
init(_ findingLocation: Finding.Location) {
46+
self.file = findingLocation.file
47+
self.line = findingLocation.line
48+
self.column = findingLocation.column
49+
}
50+
}
51+
52+
/// The severity of the diagnostic.
53+
var severity: Severity
54+
55+
/// The location where the diagnostic occurred, if known.
56+
var location: Location?
57+
58+
/// The category of the diagnostic, if any.
59+
var category: String?
60+
61+
/// The message text associated with the diagnostic.
62+
var message: String
63+
64+
var description: String {
65+
if let category = category {
66+
return "[\(category)] \(message)"
67+
} else {
68+
return message
69+
}
70+
}
71+
72+
/// Creates a new diagnostic with the given severity, location, optional category, and
73+
/// message.
74+
init(severity: Severity, location: Location?, category: String? = nil, message: String) {
75+
self.severity = severity
76+
self.location = location
77+
self.category = category
78+
self.message = message
79+
}
80+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftFormatCore
14+
import SwiftSyntax
15+
import SwiftDiagnostics
16+
17+
/// Unifies the handling of findings from the linter, parsing errors from the syntax parser, and
18+
/// generic errors from the frontend so that they are emitted in a uniform fashion.
19+
final class DiagnosticsEngine {
20+
/// The handler functions that will be called to process diagnostics that are emitted.
21+
private let handlers: [(Diagnostic) -> Void]
22+
23+
/// A Boolean value indicating whether any errors were emitted by the diagnostics engine.
24+
private(set) var hasErrors: Bool
25+
26+
/// A Boolean value indicating whether any warnings were emitted by the diagnostics engine.
27+
private(set) var hasWarnings: Bool
28+
29+
/// Creates a new diagnostics engine with the given diagnostic handlers.
30+
///
31+
/// - Parameter diagnosticsHandlers: An array of functions, each of which takes a `Diagnostic` as
32+
/// its sole argument and returns `Void`. The functions are called whenever a diagnostic is
33+
/// received by the engine.
34+
init(diagnosticsHandlers: [(Diagnostic) -> Void]) {
35+
self.handlers = diagnosticsHandlers
36+
self.hasErrors = false
37+
self.hasWarnings = false
38+
}
39+
40+
/// Emits the diagnostic by passing it to the registered handlers, and tracks whether it was an
41+
/// error or warning diagnostic.
42+
private func emit(_ diagnostic: Diagnostic) {
43+
switch diagnostic.severity {
44+
case .error: self.hasErrors = true
45+
case .warning: self.hasWarnings = true
46+
default: break
47+
}
48+
49+
for handler in handlers {
50+
handler(diagnostic)
51+
}
52+
}
53+
54+
/// Emits a generic error message.
55+
///
56+
/// - Parameters:
57+
/// - message: The message associated with the error.
58+
/// - location: The location in the source code associated with the error, or nil if there is no
59+
/// location associated with the error.
60+
func emitError(_ message: String, location: SourceLocation? = nil) {
61+
emit(
62+
Diagnostic(
63+
severity: .error,
64+
location: location.map(Diagnostic.Location.init),
65+
message: message))
66+
}
67+
68+
/// Emits a finding from the linter and any of its associated notes as diagnostics.
69+
///
70+
/// - Parameter finding: The finding that should be emitted.
71+
func consumeFinding(_ finding: Finding) {
72+
emit(diagnosticMessage(for: finding))
73+
74+
for note in finding.notes {
75+
emit(
76+
Diagnostic(
77+
severity: .note,
78+
location: note.location.map(Diagnostic.Location.init),
79+
message: "\(note.message)"))
80+
}
81+
}
82+
83+
/// Emits a diagnostic from the syntax parser and any of its associated notes.
84+
///
85+
/// - Parameter diagnostic: The syntax parser diagnostic that should be emitted.
86+
func consumeParserDiagnostic(
87+
_ diagnostic: SwiftDiagnostics.Diagnostic,
88+
_ location: SourceLocation
89+
) {
90+
emit(diagnosticMessage(for: diagnostic.diagMessage, at: location))
91+
}
92+
93+
/// Converts a diagnostic message from the syntax parser into a diagnostic message that can be
94+
/// used by the `TSCBasic` diagnostics engine and returns it.
95+
private func diagnosticMessage(
96+
for message: SwiftDiagnostics.DiagnosticMessage,
97+
at location: SourceLocation
98+
) -> Diagnostic {
99+
let severity: Diagnostic.Severity
100+
switch message.severity {
101+
case .error: severity = .error
102+
case .warning: severity = .warning
103+
case .note: severity = .note
104+
}
105+
return Diagnostic(
106+
severity: severity,
107+
location: Diagnostic.Location(location),
108+
category: nil,
109+
message: message.message)
110+
}
111+
112+
/// Converts a lint finding into a diagnostic message that can be used by the `TSCBasic`
113+
/// diagnostics engine and returns it.
114+
private func diagnosticMessage(for finding: Finding) -> Diagnostic {
115+
let severity: Diagnostic.Severity
116+
switch finding.severity {
117+
case .error: severity = .error
118+
case .warning: severity = .warning
119+
}
120+
return Diagnostic(
121+
severity: severity,
122+
location: finding.location.map(Diagnostic.Location.init),
123+
category: "\(finding.category)",
124+
message: "\(finding.message.text)")
125+
}
126+
}

Sources/swift-format/Utilities/StderrDiagnosticPrinter.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import Dispatch
1414
import Foundation
15-
import TSCBasic
1615

1716
/// Manages printing of diagnostics to standard error.
1817
final class StderrDiagnosticPrinter {
@@ -49,11 +48,7 @@ final class StderrDiagnosticPrinter {
4948
init(colorMode: ColorMode) {
5049
switch colorMode {
5150
case .auto:
52-
if let stream = stderrStream.stream as? LocalFileOutputByteStream {
53-
useColors = TerminalController.isTTY(stream)
54-
} else {
55-
useColors = false
56-
}
51+
useColors = isTTY(FileHandle.standardError)
5752
case .off:
5853
useColors = false
5954
case .on:
@@ -62,25 +57,32 @@ final class StderrDiagnosticPrinter {
6257
}
6358

6459
/// Prints a diagnostic to standard error.
65-
func printDiagnostic(_ diagnostic: TSCBasic.Diagnostic) {
60+
func printDiagnostic(_ diagnostic: Diagnostic) {
6661
printQueue.sync {
6762
let stderr = FileHandleTextOutputStream(FileHandle.standardError)
6863

69-
stderr.write("\(ansiSGR(.boldWhite))\(diagnostic.location): ")
64+
stderr.write("\(ansiSGR(.boldWhite))\(description(of: diagnostic.location)): ")
7065

71-
switch diagnostic.behavior {
66+
switch diagnostic.severity {
7267
case .error: stderr.write("\(ansiSGR(.boldRed))error: ")
7368
case .warning: stderr.write("\(ansiSGR(.boldMagenta))warning: ")
7469
case .note: stderr.write("\(ansiSGR(.boldGray))note: ")
75-
case .remark, .ignored: break
7670
}
7771

78-
let data = diagnostic.data as! UnifiedDiagnosticData
79-
if let category = data.category {
72+
if let category = diagnostic.category {
8073
stderr.write("\(ansiSGR(.boldYellow))[\(category)] ")
8174
}
82-
stderr.write("\(ansiSGR(.boldWhite))\(data.message)\(ansiSGR(.reset))\n")
75+
stderr.write("\(ansiSGR(.boldWhite))\(diagnostic.message)\(ansiSGR(.reset))\n")
76+
}
77+
}
78+
79+
/// Returns a string representation of the given diagnostic location, or a fallback string if the
80+
/// location was not known.
81+
private func description(of location: Diagnostic.Location?) -> String {
82+
if let location = location {
83+
return "\(location.file):\(location.line):\(location.column)"
8384
}
85+
return "<unknown>"
8486
}
8587

8688
/// Returns the complete ANSI sequence used to enable the given SGR if colors are enabled in the
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// Returns a value indicating whether or not the stream is a TTY.
16+
func isTTY(_ fileHandle: FileHandle) -> Bool {
17+
// The implementation of this function is adapted from `TerminalController.swift` in
18+
// swift-tools-support-core.
19+
#if os(Windows)
20+
// The TSC implementation of this function only returns `.file` or `.dumb` for Windows,
21+
// neither of which is a TTY.
22+
return false
23+
#else
24+
if ProcessInfo.processInfo.environment["TERM"] == "dumb" {
25+
return false
26+
}
27+
return isatty(fileHandle.fileDescriptor) != 0
28+
#endif
29+
}

0 commit comments

Comments
 (0)