Skip to content

Commit

Permalink
Merge pull request swiftlang#509 from allevato/remove-tsc
Browse files Browse the repository at this point in the history
Remove the `swift-tools-support-core` dependency from swift-format.
  • Loading branch information
allevato committed Jun 29, 2023
1 parent ce7db95 commit 65c8636
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 171 deletions.
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 @@ -219,15 +218,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
url: "https://github.com/apple/swift-syntax.git",
branch: "release/5.9"
),
.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

0 comments on commit 65c8636

Please sign in to comment.