File: SemanticRefactoring.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (143 lines) | stat: -rw-r--r-- 5,583 bytes parent folder | download
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
142
143
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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 LanguageServerProtocol
import SourceKitD

/// Detailed information about the result of a specific refactoring operation.
///
/// Wraps the information returned by sourcekitd's `semantic_refactoring` request, such as the necessary edits and placeholder locations.
struct SemanticRefactoring {

  /// The title of the refactoring action.
  var title: String

  /// The resulting `WorkspaceEdit` of a `semantic_refactoring` request.
  var edit: WorkspaceEdit

  init(_ title: String, _ edit: WorkspaceEdit) {
    self.title = title
    self.edit = edit
  }

  /// Create a `SemanticRefactoring` from a sourcekitd response dictionary, if possible.
  ///
  /// - Parameters:
  ///   - title: The title of the refactoring action.
  ///   - dict: Response dictionary to extract information from.
  ///   - snapshot: The snapshot that triggered the `semantic_refactoring` request.
  ///   - keys: The sourcekitd key set to use for looking up into `dict`.
  init?(_ title: String, _ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) {
    guard let categorizedEdits: SKDResponseArray = dict[keys.categorizedEdits] else {
      return nil
    }

    var textEdits = [TextEdit]()

    categorizedEdits.forEach { _, value in
      guard let edits: SKDResponseArray = value[keys.edits] else {
        return false
      }
      edits.forEach { _, value in
        // The LSP is zero based, but semantic_refactoring is one based.
        guard let startLine: Int = value[keys.line],
          let startColumn: Int = value[keys.column],
          let endLine: Int = value[keys.endLine],
          let endColumn: Int = value[keys.endColumn],
          let text: String = value[keys.text]
        else {
          return true  // continue
        }
        let startPosition = snapshot.positionOf(
          zeroBasedLine: startLine - 1,
          utf8Column: startColumn - 1
        )
        let endPosition = snapshot.positionOf(
          zeroBasedLine: endLine - 1,
          utf8Column: endColumn - 1
        )
        // Snippets are only supported in code completion.
        // Remove SourceKit placeholders in refactoring actions because they can't be represented in the editor properly.
        let textWithSnippets = rewriteSourceKitPlaceholders(in: text, clientSupportsSnippets: false)
        textEdits.append(TextEdit(range: startPosition..<endPosition, newText: textWithSnippets))
        return true
      }
      return true
    }

    guard textEdits.isEmpty == false else {
      return nil
    }

    self.title = title
    self.edit = WorkspaceEdit(changes: [snapshot.uri: textEdits])
  }
}

/// An error from a semantic refactoring request.
enum SemanticRefactoringError: Error {
  /// The underlying sourcekitd request failed with the given error.
  case responseError(ResponseError)

  /// The underlying sourcekitd reported no edits for this action.
  case noEditsNeeded(DocumentURI)
}

extension SemanticRefactoringError: CustomStringConvertible {
  var description: String {
    switch self {
    case .responseError(let error):
      return "\(error)"
    case .noEditsNeeded(let url):
      return "no edits reported for semantic refactoring action for url \(url)"
    }
  }
}

extension SwiftLanguageService {
  /// Provides detailed information about the result of a specific refactoring operation.
  ///
  /// Wraps the information returned by sourcekitd's `semantic_refactoring` request, such as the necessary edits and placeholder locations.
  ///
  /// - Parameters:
  ///   - refactorCommand: The semantic refactor `Command` that triggered this request.
  func semanticRefactoring(
    _ refactorCommand: SemanticRefactorCommand
  ) async throws -> SemanticRefactoring {
    let keys = self.keys

    let uri = refactorCommand.textDocument.uri
    let snapshot = try self.documentManager.latestSnapshot(uri)
    let line = refactorCommand.positionRange.lowerBound.line
    let utf16Column = refactorCommand.positionRange.lowerBound.utf16index
    let utf8Column = snapshot.lineTable.utf8ColumnAt(line: line, utf16Column: utf16Column)

    let skreq = sourcekitd.dictionary([
      keys.request: self.requests.semanticRefactoring,
      // Preferred name for e.g. an extracted variable.
      // Empty string means sourcekitd chooses a name automatically.
      keys.name: "",
      keys.sourceFile: uri.pseudoPath,
      // LSP is zero based, but this request is 1 based.
      keys.line: line + 1,
      keys.column: utf8Column + 1,
      keys.length: snapshot.utf8OffsetRange(of: refactorCommand.positionRange).count,
      keys.actionUID: self.sourcekitd.api.uid_get_from_cstr(refactorCommand.actionString)!,
      keys.compilerArgs: await self.buildSettings(for: snapshot.uri)?.compilerArgs as [SKDRequestValue]?,
    ])

    let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
    guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
      throw SemanticRefactoringError.noEditsNeeded(uri)
    }
    return refactor
  }
}