Skip to content

Commit

Permalink
Make CodeAction wrapper types an enum
Browse files Browse the repository at this point in the history
  • Loading branch information
rockbruno committed Jun 20, 2019
1 parent f0b5c27 commit e204e52
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 114 deletions.
50 changes: 21 additions & 29 deletions Sources/LanguageServerProtocol/CodeAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// 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
Expand Down Expand Up @@ -52,54 +52,46 @@ public struct CodeActionRequest: TextDocumentRequest, Hashable {
/// Wrapper type for the response of a CodeAction request.
/// If the client supports CodeAction literals, the encoded type will be the CodeAction array itself.
/// Otherwise, the encoded value will be an array of CodeActions' inner Command structs.
public struct CodeActionRequestResponse: ResponseType, Codable {
public var codeActions: [CodeAction]
public var commands: [Command]
public enum CodeActionRequestResponse: ResponseType, Codable, Equatable {
case codeActions([CodeAction])
case commands([Command])

public init(codeActions: [CodeAction], clientCapabilities: TextDocumentClientCapabilities.CodeAction?) {
if let literalSupport = clientCapabilities?.codeActionLiteralSupport {
let supportedKinds = literalSupport.codeActionKind.valueSet
self.codeActions = codeActions.filter {
self = .codeActions(codeActions.filter {
if let kind = $0.kind {
return supportedKinds.contains(kind)
} else {
// The client guarantees that unsupported kinds will be treated,
// so it's probably safe to include unspecified kinds into the result.
return true
}
}
self.commands = []
})
} else {
self.codeActions = []
self.commands = codeActions.compactMap { $0.command }
self = .commands(codeActions.compactMap { $0.command })
}
}

public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var codeActions = [CodeAction]()
var commands = [Command]()
while container.isAtEnd == false {
if let codeAction = try container.decodeIfPresent(CodeAction.self) {
codeActions.append(codeAction)
} else if let command = try container.decodeIfPresent(Command.self) {
commands.append(command)
} else {
let error = "CodeActionRequestResponse has neither a CodeAction or a Command."
throw DecodingError.dataCorruptedError(in: container, debugDescription: error)
}
let container = try decoder.singleValueContainer()
if let codeActions = try? container.decode([CodeAction].self) {
self = .codeActions(codeActions)
} else if let commands = try? container.decode([Command].self) {
self = .commands(commands)
} else {
let error = "CodeActionRequestResponse has neither a CodeAction or a Command."
throw DecodingError.dataCorruptedError(in: container, debugDescription: error)
}
self.codeActions = codeActions
self.commands = commands
}

public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for codeAction in codeActions {
try container.encode(codeAction)
}
for command in commands {
try container.encode(command)
var container = encoder.singleValueContainer()
switch self {
case .codeActions(let codeActions):
try container.encode(codeActions)
case .commands(let commands):
try container.encode(commands)
}
}
}
Expand Down
79 changes: 20 additions & 59 deletions Sources/LanguageServerProtocol/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
//===----------------------------------------------------------------------===//

import SKSupport
import class Foundation.NSNull

/// Represents a reference to a command identified by a string. Used as the result of
/// requests that returns actions to the user, later used as the parameter of
Expand All @@ -34,7 +33,7 @@ public struct Command: Codable, Hashable {
}
}

public enum CommandArgumentType {
public enum CommandArgumentType: Hashable {
case null
case int(Int)
case bool(Bool)
Expand All @@ -44,48 +43,6 @@ public enum CommandArgumentType {
case dictionary([String: CommandArgumentType])
}

extension CommandArgumentType: Hashable {
public static func == (lhs: CommandArgumentType, rhs: CommandArgumentType) -> Bool {
switch (lhs, rhs) {
case (.null, .null):
return true
case let (.int(lhs), .int(rhs)):
return lhs == rhs
case let (.bool(lhs), .bool(rhs)):
return lhs == rhs
case let (.double(lhs), .double(rhs)):
return lhs == rhs
case let (.string(lhs), .string(rhs)):
return lhs == rhs
case let (.array(lhs), .array(rhs)):
return lhs == rhs
case let (.dictionary(lhs), .dictionary(rhs)):
return lhs == rhs
default:
return false
}
}

public func hash(into hasher: inout Hasher) {
switch self {
case .null:
NSNull().hash(into: &hasher)
case let .int(value):
value.hash(into: &hasher)
case let .bool(value):
value.hash(into: &hasher)
case let .double(value):
value.hash(into: &hasher)
case let .string(value):
value.hash(into: &hasher)
case let .array(value):
value.hash(into: &hasher)
case let .dictionary(value):
value.hash(into: &hasher)
}
}
}

extension CommandArgumentType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
Expand Down Expand Up @@ -116,59 +73,63 @@ extension CommandArgumentType: Encodable {
switch self {
case .null:
try container.encodeNil()
case let .int(value):
case .int(let value):
try container.encode(value)
case let .bool(value):
case .bool(let value):
try container.encode(value)
case let .double(value):
case .double(let value):
try container.encode(value)
case let .string(value):
case .string(let value):
try container.encode(value)
case let .array(value):
case .array(let value):
try container.encode(value)
case let .dictionary(value):
case .dictionary(let value):
try container.encode(value)
}
}
}

extension CommandArgumentType: ExpressibleByNilLiteral {}
extension CommandArgumentType: ExpressibleByIntegerLiteral {}
extension CommandArgumentType: ExpressibleByBooleanLiteral {}
extension CommandArgumentType: ExpressibleByFloatLiteral {}
extension CommandArgumentType: ExpressibleByStringLiteral {}
extension CommandArgumentType: ExpressibleByArrayLiteral {}
extension CommandArgumentType: ExpressibleByDictionaryLiteral {}

extension CommandArgumentType {
extension CommandArgumentType: ExpressibleByNilLiteral {
public init(nilLiteral _: ()) {
self = .null
}
}

extension CommandArgumentType: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = .int(value)
}
}

extension CommandArgumentType: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = .bool(value)
}
}

extension CommandArgumentType: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .double(value)
}
}

extension CommandArgumentType: ExpressibleByStringLiteral {
public init(extendedGraphemeClusterLiteral value: String) {
self = .string(value)
}

public init(stringLiteral value: String) {
self = .string(value)
}
}

extension CommandArgumentType: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: CommandArgumentType...) {
self = .array(elements)
}
}

extension CommandArgumentType: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, CommandArgumentType)...) {
let dict = [String: CommandArgumentType](elements, uniquingKeysWith: { first, _ in first })
self = .dictionary(dict)
Expand Down
30 changes: 14 additions & 16 deletions Sources/LanguageServerProtocol/ServerCapabilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,30 +181,27 @@ public struct DocumentOnTypeFormattingOptions: Codable, Hashable {
/// Wrapper type for a server's CodeActions' capabilities.
/// If the client supports CodeAction literals, the server can return specific information about
/// how CodeActions will be sent. Otherwise, the server's capabilities are determined by a boolean.
public struct CodeActionServerCapabilities: Codable, Hashable {
public enum CodeActionServerCapabilities: Codable, Hashable {

public var supportsCodeActions: Bool
public var codeActionOptions: CodeActionOptions?
case supportsCodeActionRequests(Bool)
case supportsCodeActionRequestsWithLiterals(CodeActionOptions)

public init(clientCapabilities: TextDocumentClientCapabilities.CodeAction?,
codeActionOptions: CodeActionOptions?,
codeActionOptions: CodeActionOptions,
supportsCodeActions: Bool) {
if clientCapabilities?.codeActionLiteralSupport != nil {
self.codeActionOptions = nil
self = .supportsCodeActionRequestsWithLiterals(codeActionOptions)
} else {
self.codeActionOptions = codeActionOptions
self = .supportsCodeActionRequests(supportsCodeActions)
}
self.supportsCodeActions = supportsCodeActions
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let codeActionOptions = try? container.decode(CodeActionOptions.self) {
self.codeActionOptions = codeActionOptions
self.supportsCodeActions = true
} else if let supportsCodeActions = try? container.decode(Bool.self) {
self.supportsCodeActions = supportsCodeActions
self.codeActionOptions = nil
if let supportsCodeActions = try? container.decode(Bool.self) {
self = .supportsCodeActionRequests(supportsCodeActions)
} else if let codeActionOptions = try? container.decode(CodeActionOptions.self) {
self = .supportsCodeActionRequestsWithLiterals(codeActionOptions)
} else {
let error = "CodeActionServerCapabilities cannot be decoded: Unrecognized type."
throw DecodingError.dataCorruptedError(in: container, debugDescription: error)
Expand All @@ -213,10 +210,11 @@ public struct CodeActionServerCapabilities: Codable, Hashable {

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let codeActionOptions = codeActionOptions {
switch self {
case .supportsCodeActionRequestsWithLiterals(let codeActionOptions):
try container.encode(codeActionOptions)
} else {
try container.encode(supportsCodeActions)
case .supportsCodeActionRequests(let supportCodeActions):
try container.encode(supportCodeActions)
}
}
}
Expand Down
15 changes: 8 additions & 7 deletions Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import SKCore
import SKSupport
import Basic
import sourcekitd
import class Foundation.DispatchQueue
import Dispatch
import struct Foundation.CharacterSet

public final class SwiftLanguageServer: LanguageServer {
Expand Down Expand Up @@ -765,17 +765,18 @@ extension SwiftLanguageServer {
completion([])
return
}
var providersLeftToProcess = Set<Int>(0..<providers.count)
var codeActions = [CodeAction]()
let dispatchGroup = DispatchGroup()
(0..<providers.count).forEach { _ in dispatchGroup.enter() }
dispatchGroup.notify(queue: queue) {
completion(codeActions)
}
for i in 0..<providers.count {
providers[i](req.params) { actions in
DispatchQueue.global(qos: .userInitiated).sync(flags: .barrier) {
self.queue.sync {
codeActions += actions
}
providersLeftToProcess.remove(i)
if providersLeftToProcess.isEmpty {
completion(codeActions)
}
dispatchGroup.leave()
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions Tests/SourceKitTests/CodeActionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import XCTest

final class CodeActionTests: XCTestCase {
func testCodeActionResponseLegacySupport() {
let command = Command(title: "Title", command: "Command", arguments: [1,"f",2.2, nil])
let command = Command(title: "Title", command: "Command", arguments: [1, "text", 2.2, nil])
let codeAction = CodeAction(title: "1")
let codeAction2 = CodeAction(title: "2", command: command)

Expand Down Expand Up @@ -85,7 +85,7 @@ final class CodeActionTests: XCTestCase {
from: data)

response = .init(codeActions: actions, clientCapabilities: capabilities)
XCTAssertEqual(response.codeActions, [unspecifiedAction, refactorAction])
XCTAssertEqual(response, .codeActions([unspecifiedAction, refactorAction]))

capabilityJson =
"""
Expand All @@ -103,6 +103,15 @@ final class CodeActionTests: XCTestCase {
from: data)

response = .init(codeActions: actions, clientCapabilities: capabilities)
XCTAssertEqual(response.codeActions, [unspecifiedAction])
XCTAssertEqual(response, .codeActions([unspecifiedAction]))
}

func testCommandEncoding() {
let dictionary: CommandArgumentType = ["1": [nil, 2], "2": "text", "3": ["4": [1, 2]]]
let array: CommandArgumentType = [1, [2,"string"], dictionary]
let arguments: CommandArgumentType = [1, 2.2, "text", nil, array, dictionary]
let command = Command(title: "Command", command: "command.id", arguments: [arguments, arguments])
let decoded = try! JSONDecoder().decode(Command.self, from: JSONEncoder().encode(command))
XCTAssertEqual(decoded, command)
}
}
1 change: 1 addition & 0 deletions Tests/SourceKitTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extension CodeActionTests {
static let __allTests__CodeActionTests = [
("testCodeActionResponseLegacySupport", testCodeActionResponseLegacySupport),
("testCodeActionResponseRespectsSupportedKinds", testCodeActionResponseRespectsSupportedKinds),
("testCommandEncoding", testCommandEncoding),
]
}

Expand Down

0 comments on commit e204e52

Please sign in to comment.