Skip to content

Commit

Permalink
Merge pull request #2238 from Matejkob/annotat-source-lines-with-note…
Browse files Browse the repository at this point in the history
…s-preperations
  • Loading branch information
ahoppen authored Oct 25, 2023
2 parents 8d5fab7 + 636556c commit 0f808e7
Show file tree
Hide file tree
Showing 11 changed files with 784 additions and 193 deletions.
4 changes: 4 additions & 0 deletions Sources/SwiftDiagnostics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_swift_syntax_library(SwiftDiagnostics
DiagnosticDecorators/ANSIDiagnosticDecorator.swift
DiagnosticDecorators/BasicDiagnosticDecorator.swift
DiagnosticDecorators/DiagnosticDecorator.swift

Convenience.swift
Diagnostic.swift
DiagnosticsFormatter.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

extension DiagnosticDecorator where Self == ANSIDiagnosticDecorator {
/// - SeeAlso: ``ANSIDiagnosticDecorator``
static var ANSI: Self {
Self()
}
}

/// An implementation of the `DiagnosticDecorator` protocol that enhances various diagnostic elements—including messages,
/// buffer outlines, and code highlights—by applying severity-based prefixes and ANSI color codes.
///
/// This decorator uses ANSI codes—control characters specialized for text formatting in terminals—to provide visual cues.
@_spi(Testing) public struct ANSIDiagnosticDecorator: DiagnosticDecorator {

@_spi(Testing) public init() {}

/// Decorates a diagnostic message by appending a severity-based prefix and applying ANSI color codes.
///
/// - Parameters:
/// - message: The diagnostic message that needs to be decorated.
/// - severity: The severity level associated with the diagnostic message.
///
/// - Returns: A string that combines the severity-specific prefix and the original diagnostic message, with ANSI colorization.
///
/// ## Example
///
/// ```swift
/// let decorator = ANSIDiagnosticDecorator()
/// let decoratedMessage = decorator.decorateMessage("File not found", basedOnSeverity: .error)
/// // Output would be: "error: File not found"
/// ```
/// In this example, the "error: " prefix is colorized, likely appearing in red, while the message retains its default text color.
///
/// For a similar colorized output in the console, you can use `printf` in Bash:
/// ```bash
/// printf "\e[1;31merror: \e[1;39mFile not found\e[0;0m\n"
/// ```
@_spi(Testing) public func decorateMessage(_ message: String, basedOnSeverity severity: DiagnosticSeverity) -> String {
let severityText: String
let severityAnnotation: ANSIAnnotation

switch severity {
case .error:
severityText = "error"
severityAnnotation = .errorText

case .warning:
severityText = "warning"
severityAnnotation = .warningText

case .note:
severityText = "note"
severityAnnotation = .noteText

case .remark:
severityText = "remark"
severityAnnotation = .remarkText
}

let prefix = colorizeIfNotEmpty("\(severityText): ", usingAnnotation: severityAnnotation, resetAfterApplication: false)

return prefix + colorizeIfNotEmpty(message, usingAnnotation: .diagnosticText)
}

/// Decorates a source code buffer outline using ANSI cyan color codes.
///
/// - Parameter bufferOutline: The string representation of the source code buffer outline.
///
/// - Returns: A string featuring ANSI cyan color codes applied to the source code buffer outline.
@_spi(Testing) public func decorateBufferOutline(_ bufferOutline: String) -> String {
colorizeIfNotEmpty(bufferOutline, usingAnnotation: .bufferOutline)
}

/// Emphasizes a specific text segment within a source code snippet using ANSI color codes.
///
/// - Parameter highlight: The text segment within the source code snippet that should be emphasized.
///
/// - Returns: A tuple containing:
/// - `highlightedSourceCode`: The underlined version of the original source code snippet.
/// - `additionalHighlightedLine`: Always nil.
///
/// ## Example
///
/// ```swift
/// let decorator = ANSIDiagnosticDecorator()
/// let decoratedHighlight = decorator.decorateHighlight("let x = 10")
/// // Output would be: ["\u{1B}[4;39mlet x = 10\u{1B}[0;0m"]
/// ```
///
/// To reproduce a similar colorized output manually in the console, you can use `printf` in Bash:
/// ```bash
/// printf "\e[4;39mlet x = 10\e[0;0m\n"
/// ```
@_spi(Testing) public func decorateHighlight(_ highlight: String) -> (highlightedSourceCode: String, additionalHighlightedLine: String?) {
(highlightedSourceCode: colorizeIfNotEmpty(highlight, usingAnnotation: .sourceHighlight), additionalHighlightedLine: nil)
}

/// Applies ANSI annotation to a given text segment, if the text is not empty.
///
/// - Parameters:
/// - text: The text segment to which the annotation should be applied.
/// - annotation: The ANSI annotation to apply.
/// - resetAfter: A flag indicating whether to reset ANSI settings after applying them. Defaults to true.
///
/// - Returns: A potentially colorized version of the input text.
private func colorizeIfNotEmpty(
_ text: String,
usingAnnotation annotation: ANSIAnnotation,
resetAfterApplication resetAfter: Bool = true
) -> String {
if text.isEmpty {
return text
} else {
return annotation.applied(to: text, resetAfter: resetAfter)
}
}
}

/// Defines text attributes to be applied to console output.
private struct ANSIAnnotation {
/// Represents ANSI color codes.
enum Color: UInt8 {
case normal = 0
case black = 30
case red = 31
case green = 32
case yellow = 33
case blue = 34
case magenta = 35
case cyan = 36
case white = 37
case `default` = 39
}

/// Represents ANSI text traits.
enum Trait: UInt8 {
case normal = 0
case bold = 1
case underline = 4
}

/// The ANSI color to be used.
let color: Color

/// The ANSI text trait to be used.
let trait: Trait

/// Returns ANSI code as a string, including both trait and color.
var code: String {
"\u{001B}[\(trait.rawValue);\(color.rawValue)m"
}

/// Applies the ANSI code to a message string. Optionally resets the code after the message.
func applied(to message: String, resetAfter: Bool = true) -> String {
guard resetAfter else {
return "\(code)\(message)"
}
return "\(code)\(message)\(ANSIAnnotation.normal.code)"
}

/// The default 'normal' ANSIAnnotation used to reset styles.
static var normal: Self {
Self(color: .normal, trait: .normal)
}

/// Annotation used for the outline and line numbers of a buffer.
static var bufferOutline: Self {
Self(color: .cyan, trait: .normal)
}

/// Annotation used for highlighting source text.
static var sourceHighlight: Self {
Self(color: .default, trait: .underline)
}

/// Annotation used for making text bold, commonly used in diagnostic messages.
static var diagnosticText: Self {
Self(color: .default, trait: .bold)
}

/// Annotation used for error text.
static var errorText: Self {
Self(color: .red, trait: .bold)
}

/// Annotation used for warning text.
static var warningText: Self {
Self(color: .yellow, trait: .bold)
}

/// Annotation used for note text.
static var noteText: Self {
Self(color: .default, trait: .bold)
}

/// Annotation used for remarks or less critical text.
static var remarkText: Self {
Self(color: .blue, trait: .bold)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

extension DiagnosticDecorator where Self == BasicDiagnosticDecorator {
/// - Seealso: ``BasicDiagnosticDecorator``
static var basic: Self {
Self()
}
}

/// An implementation of the `DiagnosticDecorator` protocol that enhances diagnostic elements—such as messages,
/// buffer outlines, and code highlights—by appending severity-based prefixes.
///
/// Unlike `ANSIDiagnosticDecorator`, this decorator does not use ANSI color codes and solely relies on textual cues.
@_spi(Testing) public struct BasicDiagnosticDecorator: DiagnosticDecorator {

@_spi(Testing) public init() {}

/// Decorates a diagnostic message by appending a severity-based prefix.
///
/// - Parameters:
/// - message: The diagnostic message that needs to be decorated.
/// - severity: The severity level associated with the diagnostic message.
///
/// - Returns: A string that combines the severity-specific prefix and the original diagnostic message.
@_spi(Testing) public func decorateMessage(_ message: String, basedOnSeverity severity: DiagnosticSeverity) -> String {
let severityText: String

switch severity {
case .error:
severityText = "error"
case .warning:
severityText = "warning"
case .note:
severityText = "note"
case .remark:
severityText = "remark"
}

return severityText + ": " + message
}

/// Passes through the source code buffer outline without modification.
///
/// - Parameter bufferOutline: The string representation of the source code buffer outline.
///
/// - Returns: The original source code buffer outline.
@_spi(Testing) public func decorateBufferOutline(_ bufferOutline: String) -> String {
return bufferOutline
}

/// Passes through the text segment within a source code snippet without modification.
///
/// - Parameter highlight: The text segment within the source code snippet that should be emphasized.
///
/// - Returns: A tuple containing:
/// - `highlightedSourceCode`: The original text segment.
/// - `additionalHighlightedLine`: Always nil.
@_spi(Testing) public func decorateHighlight(_ highlight: String) -> (highlightedSourceCode: String, additionalHighlightedLine: String?) {
return (highlightedSourceCode: highlight, additionalHighlightedLine: nil)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

/// Protocol that defines a standard interface for decorating diagnostic output in source code.
///
/// This protocol is intended to be used by entities such as ``DiagnosticsFormatter`` and ``GroupedDiagnostics``
/// to apply custom decorations to diagnostic messages, buffer outlines, and code highlights.
///
/// ## Conforming to `DiagnosticDecorator`:
///
/// To conform to the `DiagnosticDecorator` protocol, you must implement three required methods:
///
/// 1. `decorateMessage(_:basedOnSeverity:)`: For decorating diagnostic messages.
/// 2. `decorateBufferOutline(_:)`: For decorating the outlines of source code buffers.
/// 3. `decorateHighlight(_:)`: For decorating individual highlights within a source code snippet.
///
/// ## Customization:
///
/// The protocol is designed to be easily customizable. Developers can create their own entities that conform
/// to `DiagnosticDecorator` to implement custom decorating logic. This allows for different visual representations,
/// such as using ANSI colors, underscores, emoji-based or other markers, for diagnostics in source code.
protocol DiagnosticDecorator {
/// Decorates a diagnostic message based on its severity level.
///
/// Implementations are expected to prepend a severity-specific prefix (e.g., "error: ", "warning: ") to the diagnostic message.
///
/// - Parameters:
/// - message: The diagnostic message that needs to be decorated.
/// - severity: The severity level associated with the diagnostic message.
///
/// - Returns: A decorated version of the diagnostic message, enhanced by visual cues like color, text styles, or other markers,
/// as well as a severity-specific prefix, based on its severity level.
func decorateMessage(_ message: String, basedOnSeverity severity: DiagnosticSeverity) -> String

/// Decorates the outline of a source code buffer to visually enhance its structure.
///
/// - Parameter bufferOutline: The string representation of the source code buffer outline.
///
/// - Returns: A decorated version of the buffer outline, improved with visual cues like color, text styles, or other markers.
func decorateBufferOutline(_ bufferOutline: String) -> String

/// Decorates a highlight within a source code snippet to emphasize it.
///
/// - Parameter highlight: The text segment within the source code snippet that should be emphasized.
///
/// - Returns: A tuple containing:
/// - `highlightedSourceCode`: A string that represents the decorated version of the original source code snippet.
/// - `additionalHighlightedLine`: An optional string containing additional lines of highlighting, if applicable.
///
/// - Note: The method returns a tuple to offer more flexibility in decorating highlights.
/// This allows for a variety of techniques to be used, such as ANSI codes for color
/// and additional lines for contextual emphasis, which will be combined during the rendering process.
func decorateHighlight(_ highlight: String) -> (highlightedSourceCode: String, additionalHighlightedLine: String?)
}

extension DiagnosticDecorator {
/// Decorates a ``DiagnosticMessage`` instance by delegating to the `decorateMessage(_:basedOnSeverity:)` method.
///
/// - Parameter diagnosticMessage: The ``DiagnosticMessage`` instance that encapsulates both the message and its severity level.
///
/// - Returns: A decorated version of the diagnostic message, determined by its severity level.
func decorateDiagnosticMessage(_ diagnosticMessage: DiagnosticMessage) -> String {
decorateMessage(diagnosticMessage.message, basedOnSeverity: diagnosticMessage.severity)
}
}
Loading

0 comments on commit 0f808e7

Please sign in to comment.