Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Refactor Request to make it resilience. #428

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ excluded:
- Source/SourceKittenFramework/library_wrapper_Documentation.swift
- Source/SourceKittenFramework/library_wrapper_Index.swift
- Source/SourceKittenFramework/library_wrapper_sourcekitd.swift
- Source/SourceKittenFramework/SourceKitDef.swift
opt_in_rules:
- attributes
- closure_end_indentation
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ docker_test:
docker_test_4:
docker run -v `pwd`:`pwd` -w `pwd` --rm norionomura/swift:40 swift test

generate_sourcekit_defines:
clang -E -P Templates/TypeDefs.h -o Templates/UID.swift
sourcery --sources Templates/UID.swift --templates Templates/AutoEnumNameFix.stencil --output Templates/UID.swift
mv Templates/UID.swift Source/SourceKittenFramework

# http://irace.me/swift-profiling/
display_compilation_time:
$(BUILD_TOOL) $(XCODEFLAGS) OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" clean build-for-testing | grep -E ^[1-9]{1}[0-9]*.[0-9]+ms | sort -n
Expand Down
180 changes: 9 additions & 171 deletions Source/SourceKittenFramework/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import Foundation
import SourceKit
#endif

// swiftlint:disable file_length
// This file could easily be split up

public protocol SourceKitRepresentable {
func isEqualTo(_ rhs: SourceKitRepresentable) -> Bool
}
Expand Down Expand Up @@ -189,176 +186,17 @@ private extension String {
}
}

/// Represents a SourceKit request.
public enum Request {
/// An `editor.open` request for the given File.
case editorOpen(file: File)
/// A `cursorinfo` request for an offset in the given file, using the `arguments` given.
case cursorInfo(file: String, offset: Int64, arguments: [String])
/// A custom request by passing in the sourcekitd_object_t directly.
case customRequest(request: sourcekitd_object_t)
/// A request generated by sourcekit using the yaml representation.
case yamlRequest(yaml: String)
/// A `codecomplete` request by passing in the file name, contents, offset
/// for which to generate code completion options and array of compiler arguments.
case codeCompletionRequest(file: String, contents: String, offset: Int64, arguments: [String])
/// ObjC Swift Interface
case interface(file: String, uuid: String, arguments: [String])
/// Find USR
case findUSR(file: String, usr: String)
/// Index
case index(file: String, arguments: [String])
/// Format
case format(file: String, line: Int64, useTabs: Bool, indentWidth: Int64)
/// ReplaceText
case replaceText(file: String, offset: Int64, length: Int64, sourceText: String)
/// A documentation request for the given source text.
case docInfo(text: String, arguments: [String])
/// A documentation request for the given module.
case moduleInfo(module: String, arguments: [String])

fileprivate var sourcekitObject: sourcekitd_object_t {
let dict: [sourcekitd_uid_t: sourcekitd_object_t?]
switch self {
case .editorOpen(let file):
if let path = file.path {
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.open")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(path),
sourcekitd_uid_get_from_cstr("key.sourcefile")!: sourcekitd_request_string_create(path)
]
} else {
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.open")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(String(file.contents.hash)),
sourcekitd_uid_get_from_cstr("key.sourcetext")!: sourcekitd_request_string_create(file.contents)
]
}
case .cursorInfo(let file, let offset, let arguments):
var compilerargs = arguments.map({ sourcekitd_request_string_create($0) })
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.cursorinfo")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.sourcefile")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.offset")!: sourcekitd_request_int64_create(offset),
sourcekitd_uid_get_from_cstr("key.compilerargs")!: sourcekitd_request_array_create(&compilerargs, compilerargs.count)
]
case .customRequest(let request):
return request
case .yamlRequest(let yaml):
return sourcekitd_request_create_from_yaml(yaml, nil)!
case .codeCompletionRequest(let file, let contents, let offset, let arguments):
var compilerargs = arguments.map({ sourcekitd_request_string_create($0) })
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.codecomplete")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.sourcefile")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.sourcetext")!: sourcekitd_request_string_create(contents),
sourcekitd_uid_get_from_cstr("key.offset")!: sourcekitd_request_int64_create(offset),
sourcekitd_uid_get_from_cstr("key.compilerargs")!: sourcekitd_request_array_create(&compilerargs, compilerargs.count)
]
case .interface(let file, let uuid, var arguments):
if !arguments.contains("-x") {
arguments.append(contentsOf: ["-x", "objective-c"])
}
if !arguments.contains("-isysroot") {
arguments.append(contentsOf: ["-isysroot", sdkPath()])
}
var compilerargs = ([file] + arguments).map({ sourcekitd_request_string_create($0) })
dict = [
sourcekitd_uid_get_from_cstr("key.request")!:
sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.open.interface.header")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(uuid),
sourcekitd_uid_get_from_cstr("key.filepath")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.compilerargs")!: sourcekitd_request_array_create(&compilerargs, compilerargs.count)
]
case .findUSR(let file, let usr):
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.find_usr")!),
sourcekitd_uid_get_from_cstr("key.usr")!: sourcekitd_request_string_create(usr),
sourcekitd_uid_get_from_cstr("key.sourcefile")!: sourcekitd_request_string_create(file)
]
case .index(let file, let arguments):
var compilerargs = arguments.map({ sourcekitd_request_string_create($0) })
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.indexsource")!),
sourcekitd_uid_get_from_cstr("key.sourcefile")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.compilerargs")!: sourcekitd_request_array_create(&compilerargs, compilerargs.count)
]
case .format(let file, let line, let useTabs, let indentWidth):
let formatOptions = [
sourcekitd_uid_get_from_cstr("key.editor.format.indentwidth")!: sourcekitd_request_int64_create(indentWidth),
sourcekitd_uid_get_from_cstr("key.editor.format.tabwidth")!: sourcekitd_request_int64_create(indentWidth),
sourcekitd_uid_get_from_cstr("key.editor.format.usetabs")!: sourcekitd_request_int64_create(useTabs ? 1 : 0)
]
var formatOptionsKeys = Array(formatOptions.keys.map({ $0 as sourcekitd_uid_t? }))
var formatOptionsValues = Array(formatOptions.values)
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.formattext")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.line")!: sourcekitd_request_int64_create(line),
sourcekitd_uid_get_from_cstr("key.editor.format.options")!:
sourcekitd_request_dictionary_create(&formatOptionsKeys, &formatOptionsValues, formatOptions.count)
]
case .replaceText(let file, let offset, let length, let sourceText):
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.replacetext")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.offset")!: sourcekitd_request_int64_create(offset),
sourcekitd_uid_get_from_cstr("key.length")!: sourcekitd_request_int64_create(length),
sourcekitd_uid_get_from_cstr("key.sourcetext")!: sourcekitd_request_string_create(sourceText)
]
case .docInfo(let text, let arguments):
var compilerargs = arguments.map({ sourcekitd_request_string_create($0) })
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.docinfo")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(NSUUID().uuidString),
sourcekitd_uid_get_from_cstr("key.compilerargs")!: sourcekitd_request_array_create(&compilerargs, compilerargs.count),
sourcekitd_uid_get_from_cstr("key.sourcetext")!: sourcekitd_request_string_create(text)
]
case .moduleInfo(let module, let arguments):
var compilerargs = arguments.map({ sourcekitd_request_string_create($0) })
dict = [
sourcekitd_uid_get_from_cstr("key.request")!: sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.docinfo")!),
sourcekitd_uid_get_from_cstr("key.name")!: sourcekitd_request_string_create(NSUUID().uuidString),
sourcekitd_uid_get_from_cstr("key.compilerargs")!: sourcekitd_request_array_create(&compilerargs, compilerargs.count),
sourcekitd_uid_get_from_cstr("key.modulename")!: sourcekitd_request_string_create(module)
]
}
var keys = Array(dict.keys.map({ $0 as sourcekitd_uid_t? }))
var values = Array(dict.values)
return sourcekitd_request_dictionary_create(&keys, &values, dict.count)!
}

/**
Create a Request.CursorInfo.sourcekitObject() from a file path and compiler arguments.

- parameter filePath: Path of the file to create request.
- parameter arguments: Compiler arguments.

- returns: sourcekitd_object_t representation of the Request, if successful.
*/
internal static func cursorInfoRequest(filePath: String?, arguments: [String]) -> sourcekitd_object_t? {
if let path = filePath {
return Request.cursorInfo(file: path, offset: 0, arguments: arguments).sourcekitObject
}
return nil
}
public protocol RequestType {
func sourcekitObject() -> sourcekitd_object_t
}

/**
Send a Request.CursorInfo by updating its offset. Returns SourceKit response if successful.
/// Represents a SourceKit request.
public struct Request {

- parameter cursorInfoRequest: sourcekitd_object_t representation of Request.CursorInfo
- parameter offset: Offset to update request.
public let type: RequestType

- returns: SourceKit response if successful.
*/
internal static func send(cursorInfoRequest: sourcekitd_object_t, atOffset offset: Int64) -> [String: SourceKitRepresentable]? {
if offset == 0 {
return nil
}
sourcekitd_request_dictionary_set_int64(cursorInfoRequest, sourcekitd_uid_get_from_cstr(SwiftDocKey.offset.rawValue)!, offset)
return try? Request.customRequest(request: cursorInfoRequest).failableSend()
fileprivate var sourcekitObject: sourcekitd_object_t {
return type.sourcekitObject()
}

/**
Expand Down Expand Up @@ -429,7 +267,7 @@ public enum Request {
}
}

// MARK: CustomStringConvertible
// MARK: - CustomStringConvertible

extension Request: CustomStringConvertible {
/// A textual representation of `Request`.
Expand Down
87 changes: 87 additions & 0 deletions Source/SourceKittenFramework/RequestBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// RequestBuilder.swift
// sourcekitten
//
// Created by Zheng Li on 06/10/2017.
// Copyright © 2017 SourceKitten. All rights reserved.
//

import Foundation
#if SWIFT_PACKAGE
import SourceKit
#endif

public class RequestBuilder {
private var dict: [sourcekitd_uid_t: sourcekitd_object_t] = [:]

public init(type: UID.Request) {
dict[transform(key: "key.request")] = sourcekitd_request_uid_create(transform(key: type.description))
}

public subscript(_ key: UID.Key) -> Any? {
get { return dict[transform(key: key.description)] }
set { set(newValue, for: key.description) }
}

public func makeRequest() -> sourcekitd_object_t {
var keys = Array(dict.keys.map({ $0 as sourcekitd_uid_t? }))
var values = Array(dict.values.map({ $0 as sourcekitd_object_t? }))
return sourcekitd_request_dictionary_create(&keys, &values, dict.count)!
}

private func set(_ value: Any?, for key: String) {
if let value = value {
if let string = value as? String, key == "key.request" {
dict[transform(key: "key.request")] = sourcekitd_request_uid_create(transform(key: string))
} else {
dict[transform(key: key)] = transform(any: value)
}
} else {
dict[transform(key: key)] = nil
}
}
}

private func transform(key: String) -> sourcekitd_uid_t {
return sourcekitd_uid_get_from_cstr(key)!
}

private func transform(string: String) -> sourcekitd_object_t {
return sourcekitd_request_string_create(string)!
}

private func transform(bool: Bool) -> sourcekitd_object_t {
return sourcekitd_request_int64_create(bool ? 1 : 0)!
}

private func transform(integer: Int64) -> sourcekitd_object_t {
return sourcekitd_request_int64_create(integer)!
}

private func transform(array: [Any]) -> sourcekitd_object_t {
var array = array.map(transform(any:)).map { $0 as sourcekitd_object_t? }
return sourcekitd_request_array_create(&array, array.count)!
}

private func transform(dictionary: [String: Any]) -> sourcekitd_object_t {
var keys = Array(dictionary.keys).map(transform(key:)).map { $0 as sourcekitd_uid_t? }
var values = Array(dictionary.values).map(transform(any:)).map { $0 as sourcekitd_object_t? }
return sourcekitd_request_dictionary_create(&keys, &values, dictionary.count)!
}

private func transform(any: Any) -> sourcekitd_object_t {
switch any {
case let string as String:
return transform(string: string)
case let bool as Bool:
return transform(bool: bool)
case let integer as Int64:
return transform(integer: integer)
case let array as [Any]:
return transform(array: array)
case let dictionary as [String: Any]:
return transform(dictionary: dictionary)
default:
fatalError("RequestBuilder: Unsupported value type: \(type(of: any)) value: \(any)")
}
}
49 changes: 49 additions & 0 deletions Source/SourceKittenFramework/Requests/Request+CodeComplete.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Request+CodeCompletion.swift
// SourceKittenFramework
//
// Created by Zheng Li on 25/11/2017.
// Copyright © 2017 SourceKitten. All rights reserved.
//

import Foundation

// swiftlint:disable nesting
extension Request {
/// A `codecomplete` request by passing in the file name, contents, offset
/// for which to generate code completion options and array of compiler arguments.
public struct CodeComplete: RequestType {
public struct Source {
public let sourceFile: String?
public let sourceText: String?

public static func path(_ path: String) -> Source {
return Source(sourceFile: path, sourceText: nil)
}

public static func text(_ text: String, name: String? = nil) -> Source {
return Source(sourceFile: name, sourceText: text)
}
}
public let source: Source
public let offset: Int64
public let arguments: [String]

public func sourcekitObject() -> sourcekitd_object_t {
let requestBuilder = RequestBuilder(type: .codeComplete)
requestBuilder[.sourceFile] = source.sourceFile
requestBuilder[.sourceText] = source.sourceText
requestBuilder[.offset] = offset
requestBuilder[.compilerArgs] = arguments
return requestBuilder.makeRequest()
}
}

public static func codeCompletionRequest(file: String, contents: String, offset: Int64, arguments: [String]) -> Request {
return .codeComplete(source: .text(contents, name: file), offset: offset, arguments: arguments)
}

public static func codeComplete(source: CodeComplete.Source, offset: Int64, arguments: [String]) -> Request {
return Request(type: CodeComplete(source: source, offset: offset, arguments: arguments))
}
}