Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the swift-tools-support-core dependency from swift-format. #509

Merged
merged 1 commit into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "TSCBasic", package: "swift-tools-support-core"),
]
),

Expand Down Expand Up @@ -223,15 +222,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
url: "https://github.com/apple/swift-syntax.git",
branch: "main"
),
.package(
url: "https://github.com/apple/swift-tools-support-core.git",
exact: Version("0.4.0")
),
]
} else {
package.dependencies += [
.package(path: "../swift-argument-parser"),
.package(path: "../swift-syntax"),
.package(path: "../swift-tools-support-core"),
]
}
3 changes: 2 additions & 1 deletion Sources/swift-format/Frontend/FormatFrontend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class FormatFrontend: Frontend {
return
}

let diagnosticHandler: (Diagnostic, SourceLocation) -> () = { (diagnostic, location) in
let diagnosticHandler: (SwiftDiagnostics.Diagnostic, SourceLocation) -> () = {
(diagnostic, location) in
guard !self.lintFormatOptions.ignoreUnparsableFiles else {
// No diagnostics should be emitted in this mode.
return
Expand Down
4 changes: 2 additions & 2 deletions Sources/swift-format/Frontend/Frontend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Frontend {
final let diagnosticPrinter: StderrDiagnosticPrinter

/// The diagnostic engine to which warnings and errors will be emitted.
final let diagnosticsEngine: UnifiedDiagnosticsEngine
final let diagnosticsEngine: DiagnosticsEngine

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

/// Runs the linter or formatter over the inputs.
Expand Down
80 changes: 80 additions & 0 deletions Sources/swift-format/Utilities/Diagnostic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftFormatCore
import SwiftSyntax

/// Diagnostic data that retains the separation of a finding category (if present) from the rest of
/// the message, allowing diagnostic printers that want to print those values separately to do so.
struct Diagnostic {
/// The severity of the diagnostic.
enum Severity {
case note
case warning
case error
}

/// Represents the location of a diagnostic.
struct Location {
/// The file path associated with the diagnostic.
var file: String

/// The 1-based line number where the diagnostic occurred.
var line: Int

/// The 1-based column number where the diagnostic occurred.
var column: Int

/// Creates a new diagnostic location from the given source location.
init(_ sourceLocation: SourceLocation) {
self.file = sourceLocation.file!
self.line = sourceLocation.line!
self.column = sourceLocation.column!
}

/// Creates a new diagnostic location with the given finding location.
init(_ findingLocation: Finding.Location) {
self.file = findingLocation.file
self.line = findingLocation.line
self.column = findingLocation.column
}
}

/// The severity of the diagnostic.
var severity: Severity

/// The location where the diagnostic occurred, if known.
var location: Location?

/// The category of the diagnostic, if any.
var category: String?

/// The message text associated with the diagnostic.
var message: String

var description: String {
if let category = category {
return "[\(category)] \(message)"
} else {
return message
}
}

/// Creates a new diagnostic with the given severity, location, optional category, and
/// message.
init(severity: Severity, location: Location?, category: String? = nil, message: String) {
self.severity = severity
self.location = location
self.category = category
self.message = message
}
}
126 changes: 126 additions & 0 deletions Sources/swift-format/Utilities/DiagnosticsEngine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftFormatCore
import SwiftSyntax
import SwiftDiagnostics

/// Unifies the handling of findings from the linter, parsing errors from the syntax parser, and
/// generic errors from the frontend so that they are emitted in a uniform fashion.
final class DiagnosticsEngine {
/// The handler functions that will be called to process diagnostics that are emitted.
private let handlers: [(Diagnostic) -> Void]

/// A Boolean value indicating whether any errors were emitted by the diagnostics engine.
private(set) var hasErrors: Bool

/// A Boolean value indicating whether any warnings were emitted by the diagnostics engine.
private(set) var hasWarnings: Bool

/// Creates a new diagnostics engine with the given diagnostic handlers.
///
/// - Parameter diagnosticsHandlers: An array of functions, each of which takes a `Diagnostic` as
/// its sole argument and returns `Void`. The functions are called whenever a diagnostic is
/// received by the engine.
init(diagnosticsHandlers: [(Diagnostic) -> Void]) {
self.handlers = diagnosticsHandlers
self.hasErrors = false
self.hasWarnings = false
}

/// Emits the diagnostic by passing it to the registered handlers, and tracks whether it was an
/// error or warning diagnostic.
private func emit(_ diagnostic: Diagnostic) {
switch diagnostic.severity {
case .error: self.hasErrors = true
case .warning: self.hasWarnings = true
default: break
}

for handler in handlers {
handler(diagnostic)
}
}

/// Emits a generic error message.
///
/// - Parameters:
/// - message: The message associated with the error.
/// - location: The location in the source code associated with the error, or nil if there is no
/// location associated with the error.
func emitError(_ message: String, location: SourceLocation? = nil) {
emit(
Diagnostic(
severity: .error,
location: location.map(Diagnostic.Location.init),
message: message))
}

/// Emits a finding from the linter and any of its associated notes as diagnostics.
///
/// - Parameter finding: The finding that should be emitted.
func consumeFinding(_ finding: Finding) {
emit(diagnosticMessage(for: finding))

for note in finding.notes {
emit(
Diagnostic(
severity: .note,
location: note.location.map(Diagnostic.Location.init),
message: "\(note.message)"))
}
}

/// Emits a diagnostic from the syntax parser and any of its associated notes.
///
/// - Parameter diagnostic: The syntax parser diagnostic that should be emitted.
func consumeParserDiagnostic(
_ diagnostic: SwiftDiagnostics.Diagnostic,
_ location: SourceLocation
) {
emit(diagnosticMessage(for: diagnostic.diagMessage, at: location))
}

/// Converts a diagnostic message from the syntax parser into a diagnostic message that can be
/// used by the `TSCBasic` diagnostics engine and returns it.
private func diagnosticMessage(
for message: SwiftDiagnostics.DiagnosticMessage,
at location: SourceLocation
) -> Diagnostic {
let severity: Diagnostic.Severity
switch message.severity {
case .error: severity = .error
case .warning: severity = .warning
case .note: severity = .note
}
return Diagnostic(
severity: severity,
location: Diagnostic.Location(location),
category: nil,
message: message.message)
}

/// Converts a lint finding into a diagnostic message that can be used by the `TSCBasic`
/// diagnostics engine and returns it.
private func diagnosticMessage(for finding: Finding) -> Diagnostic {
let severity: Diagnostic.Severity
switch finding.severity {
case .error: severity = .error
case .warning: severity = .warning
}
return Diagnostic(
severity: severity,
location: finding.location.map(Diagnostic.Location.init),
category: "\(finding.category)",
message: "\(finding.message.text)")
}
}
28 changes: 15 additions & 13 deletions Sources/swift-format/Utilities/StderrDiagnosticPrinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import Dispatch
import Foundation
import TSCBasic

/// Manages printing of diagnostics to standard error.
final class StderrDiagnosticPrinter {
Expand Down Expand Up @@ -49,11 +48,7 @@ final class StderrDiagnosticPrinter {
init(colorMode: ColorMode) {
switch colorMode {
case .auto:
if let stream = stderrStream.stream as? LocalFileOutputByteStream {
useColors = TerminalController.isTTY(stream)
} else {
useColors = false
}
useColors = isTTY(FileHandle.standardError)
case .off:
useColors = false
case .on:
Expand All @@ -62,25 +57,32 @@ final class StderrDiagnosticPrinter {
}

/// Prints a diagnostic to standard error.
func printDiagnostic(_ diagnostic: TSCBasic.Diagnostic) {
func printDiagnostic(_ diagnostic: Diagnostic) {
printQueue.sync {
let stderr = FileHandleTextOutputStream(FileHandle.standardError)

stderr.write("\(ansiSGR(.boldWhite))\(diagnostic.location): ")
stderr.write("\(ansiSGR(.boldWhite))\(description(of: diagnostic.location)): ")

switch diagnostic.behavior {
switch diagnostic.severity {
case .error: stderr.write("\(ansiSGR(.boldRed))error: ")
case .warning: stderr.write("\(ansiSGR(.boldMagenta))warning: ")
case .note: stderr.write("\(ansiSGR(.boldGray))note: ")
case .remark, .ignored: break
}

let data = diagnostic.data as! UnifiedDiagnosticData
if let category = data.category {
if let category = diagnostic.category {
stderr.write("\(ansiSGR(.boldYellow))[\(category)] ")
}
stderr.write("\(ansiSGR(.boldWhite))\(data.message)\(ansiSGR(.reset))\n")
stderr.write("\(ansiSGR(.boldWhite))\(diagnostic.message)\(ansiSGR(.reset))\n")
}
}

/// Returns a string representation of the given diagnostic location, or a fallback string if the
/// location was not known.
private func description(of location: Diagnostic.Location?) -> String {
if let location = location {
return "\(location.file):\(location.line):\(location.column)"
}
return "<unknown>"
}

/// Returns the complete ANSI sequence used to enable the given SGR if colors are enabled in the
Expand Down
29 changes: 29 additions & 0 deletions Sources/swift-format/Utilities/TTY.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation

/// Returns a value indicating whether or not the stream is a TTY.
func isTTY(_ fileHandle: FileHandle) -> Bool {
// The implementation of this function is adapted from `TerminalController.swift` in
// swift-tools-support-core.
#if os(Windows)
// The TSC implementation of this function only returns `.file` or `.dumb` for Windows,
// neither of which is a TTY.
return false
#else
if ProcessInfo.processInfo.environment["TERM"] == "dumb" {
return false
}
return isatty(fileHandle.fileDescriptor) != 0
#endif
}
Loading