File: FixIt.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (141 lines) | stat: -rw-r--r-- 5,284 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#if swift(>=6)
public import SwiftSyntax
#else
import SwiftSyntax
#endif

/// Types conforming to this protocol represent Fix-It messages that can be
/// shown to the client.
/// The messages should describe the change that the Fix-It will perform
public protocol FixItMessage: Sendable {
  /// The Fix-It message that should be displayed in the client.
  var message: String { get }

  /// See ``MessageID``.
  var fixItID: MessageID { get }
}

/// Types conforming to this protocol provide the data required for replacing a child node of a parent node.
///
/// Conforming types should ensure the child of ``parent`` to be replaced at ``replacementRange`` is type-compatible
/// with ``newChild``. Conforming types are stored as type-erased existentials (i.e. `any ReplacingChildData`) in
///  ``FixIt/Change/replaceChild(data:)`` to keep ``FixIt`` type-erased.
public protocol ReplacingChildData: Sendable {
  associatedtype Parent: SyntaxProtocol
  associatedtype Child: SyntaxProtocol

  /// The node whose child node at ``replacementRange`` to be replaced by ``newChild``.
  var parent: Parent { get }

  /// The node to replace the child node of ``parent`` at ``replacementRange``.
  var newChild: Child { get }

  /// The absolute position range of the child node to be replaced.
  ///
  /// If a nil child node is to be replaced, conforming types should provide a zero-length range with both bounds
  /// denoting the start position of ``newChild`` in ``parent`` after replacement.
  var replacementRange: Range<AbsolutePosition> { get }
}

/// A Fix-It that can be applied to resolve a diagnostic.
public struct FixIt: Sendable {
  public enum Change: Sendable {
    struct ReplacingOptionalChildData<Parent: SyntaxProtocol, Child: SyntaxProtocol>: ReplacingChildData {
      let parent: Parent
      let newChild: Child
      let keyPath: WritableKeyPath<Parent, Child?> & Sendable

      var replacementRange: Range<AbsolutePosition> {
        // need to upcast keyPath to strip Sendable for older Swift versions
        let keyPath: WritableKeyPath<Parent, Child?> = keyPath
        if let oldChild = parent[keyPath: keyPath] {
          return oldChild.range
        } else {
          let newChild = parent.with(keyPath, newChild)[keyPath: keyPath]!
          return newChild.position..<newChild.position
        }
      }
    }

    /// Replace `oldNode` by `newNode`.
    case replace(oldNode: Syntax, newNode: Syntax)
    /// Replace the leading trivia on the given token
    case replaceLeadingTrivia(token: TokenSyntax, newTrivia: Trivia)
    /// Replace the trailing trivia on the given token
    case replaceTrailingTrivia(token: TokenSyntax, newTrivia: Trivia)
    /// Replace the child node of the given parent node at the given replacement range with the given new child node
    case replaceChild(data: any ReplacingChildData)
  }

  /// A description of what this Fix-It performs.
  public let message: FixItMessage

  /// The changes that need to be performed when the Fix-It is applied.
  public let changes: [Change]

  public init(message: FixItMessage, changes: [Change]) {
    precondition(!changes.isEmpty, "A Fix-It must have at least one change associated with it")
    self.message = message
    self.changes = changes
  }
}

extension FixIt {
  /// The edits represent the non-overlapping textual edits that need to be performed when the Fix-It is applied.
  public var edits: [SourceEdit] {
    var existingEdits = [SourceEdit]()
    for change in changes {
      let edit = change.edit
      let isOverlapping = existingEdits.contains { edit.range.overlaps($0.range) }
      if !isOverlapping {
        // The edit overlaps with the previous edit. We can't apply both
        // without conflicts. Apply the one that's listed first and drop the
        // later edit.
        existingEdits.append(edit)
      }
    }
    return existingEdits
  }
}

private extension FixIt.Change {
  var edit: SourceEdit {
    switch self {
    case .replace(let oldNode, let newNode):
      return SourceEdit(
        range: oldNode.position..<oldNode.endPosition,
        replacement: newNode.description
      )

    case .replaceLeadingTrivia(let token, let newTrivia):
      return SourceEdit(
        range: token.position..<token.positionAfterSkippingLeadingTrivia,
        replacement: newTrivia.description
      )

    case .replaceTrailingTrivia(let token, let newTrivia):
      return SourceEdit(
        range: token.endPositionBeforeTrailingTrivia..<token.endPosition,
        replacement: newTrivia.description
      )

    case .replaceChild(let replacingChildData):
      return SourceEdit(
        range: replacingChildData.replacementRange,
        replacement: replacingChildData.newChild.description
      )
    }
  }
}