mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
361 lines
9.4 KiB
Swift
361 lines
9.4 KiB
Swift
//===----------------------------------------------------------------------===//
|
||
//
|
||
// 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
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
@_spi(SourceKitLSP) import LanguageServerProtocol
|
||
@_spi(SourceKitLSP) import SKLogging
|
||
import SKTestSupport
|
||
import SourceKitLSP
|
||
import SwiftExtensions
|
||
import ToolchainRegistry
|
||
import XCTest
|
||
|
||
import class TSCBasic.Process
|
||
|
||
final class FormattingTests: SourceKitLSPTestCase {
|
||
func testFormatting() async throws {
|
||
let testClient = try await TestSourceKitLSPClient()
|
||
let uri = DocumentURI(for: .swift)
|
||
|
||
let source = """
|
||
struct S {
|
||
var foo: Int
|
||
var bar: Int
|
||
}
|
||
"""
|
||
testClient.openDocument(source, uri: uri)
|
||
|
||
let response = try await testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 3, insertSpaces: true)
|
||
)
|
||
)
|
||
|
||
let edits = try XCTUnwrap(response)
|
||
let formattedSource = apply(edits: edits, to: source)
|
||
|
||
XCTAssert(edits.allSatisfy { $0.newText.allSatisfy(\.isWhitespace) })
|
||
XCTAssertEqual(
|
||
formattedSource,
|
||
"""
|
||
struct S {
|
||
var foo: Int
|
||
var bar: Int
|
||
}
|
||
|
||
"""
|
||
)
|
||
}
|
||
|
||
func testFormattingNoEdits() async throws {
|
||
let testClient = try await TestSourceKitLSPClient()
|
||
let uri = DocumentURI(for: .swift)
|
||
|
||
testClient.openDocument(
|
||
"""
|
||
struct S {
|
||
var foo: Int
|
||
}
|
||
|
||
""",
|
||
uri: uri
|
||
)
|
||
|
||
let response = try await testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
)
|
||
|
||
let edits = try XCTUnwrap(response)
|
||
XCTAssertEqual(edits.count, 0)
|
||
}
|
||
|
||
func testConfigFileOnDisk() async throws {
|
||
// We pick up an invalid swift-format configuration file and thus don't set the user-provided options.
|
||
let project = try await MultiFileTestProject(files: [
|
||
".swift-format": """
|
||
{
|
||
"version": 1,
|
||
"indentation": {
|
||
"spaces": 1
|
||
}
|
||
}
|
||
""",
|
||
"test.swift": """
|
||
struct Root {
|
||
1️⃣var bar = 123
|
||
}
|
||
|
||
""",
|
||
])
|
||
let (uri, positions) = try project.openDocument("test.swift")
|
||
|
||
let response = try await project.testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
)
|
||
XCTAssertEqual(
|
||
response,
|
||
[
|
||
TextEdit(range: Range(positions["1️⃣"]), newText: " ")
|
||
]
|
||
)
|
||
}
|
||
|
||
func testConfigFileInParentDirectory() async throws {
|
||
// We pick up an invalid swift-format configuration file and thus don't set the user-provided options.
|
||
let project = try await MultiFileTestProject(files: [
|
||
".swift-format": """
|
||
{
|
||
"version": 1,
|
||
"indentation": {
|
||
"spaces": 1
|
||
}
|
||
}
|
||
""",
|
||
"sub/test.swift": """
|
||
struct Root {
|
||
1️⃣var bar = 123
|
||
}
|
||
|
||
""",
|
||
])
|
||
let (uri, positions) = try project.openDocument("test.swift")
|
||
|
||
let response = try await project.testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
)
|
||
XCTAssertEqual(
|
||
response,
|
||
[
|
||
TextEdit(range: Range(positions["1️⃣"]), newText: " ")
|
||
]
|
||
)
|
||
}
|
||
|
||
func testConfigFileInNestedDirectory() async throws {
|
||
// We pick up an invalid swift-format configuration file and thus don't set the user-provided options.
|
||
let project = try await MultiFileTestProject(files: [
|
||
".swift-format": """
|
||
{
|
||
"version": 1,
|
||
"indentation": {
|
||
"spaces": 1
|
||
}
|
||
},
|
||
""",
|
||
"sub/.swift-format": """
|
||
{
|
||
"version": 1,
|
||
"indentation": {
|
||
"spaces": 3
|
||
}
|
||
}
|
||
""",
|
||
"sub/test.swift": """
|
||
struct Root {
|
||
1️⃣var bar = 123
|
||
}
|
||
|
||
""",
|
||
])
|
||
let (uri, positions) = try project.openDocument("test.swift")
|
||
|
||
let response = try await project.testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
)
|
||
XCTAssertEqual(
|
||
response,
|
||
[
|
||
TextEdit(range: Range(positions["1️⃣"]), newText: " ")
|
||
]
|
||
)
|
||
}
|
||
|
||
func testInvalidConfigurationFile() async throws {
|
||
// We pick up an invalid swift-format configuration file and thus don't set the user-provided options.
|
||
// The swift-format default is 2 spaces.
|
||
let project = try await MultiFileTestProject(files: [
|
||
".swift-format": "",
|
||
"test.swift": """
|
||
struct Root {
|
||
1️⃣var bar = 123
|
||
}
|
||
|
||
""",
|
||
])
|
||
let (uri, _) = try project.openDocument("test.swift")
|
||
|
||
await assertThrowsError(
|
||
try await project.testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 3, insertSpaces: true)
|
||
)
|
||
)
|
||
)
|
||
}
|
||
|
||
func testInsertAndRemove() async throws {
|
||
let testClient = try await TestSourceKitLSPClient()
|
||
let uri = DocumentURI(for: .swift)
|
||
|
||
let source = """
|
||
public extension Example {
|
||
func function() {}
|
||
}
|
||
|
||
"""
|
||
testClient.openDocument(source, uri: uri)
|
||
|
||
let response = try await testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
)
|
||
|
||
let edits = try XCTUnwrap(response)
|
||
let formattedSource = apply(edits: edits, to: source)
|
||
|
||
XCTAssertEqual(
|
||
formattedSource,
|
||
"""
|
||
extension Example {
|
||
public func function() {}
|
||
}
|
||
|
||
"""
|
||
)
|
||
}
|
||
|
||
func testMultiLineStringInsertion() async throws {
|
||
let testClient = try await TestSourceKitLSPClient()
|
||
let uri = DocumentURI(for: .swift)
|
||
|
||
let source = #"""
|
||
_ = [
|
||
Node(
|
||
documentation: """
|
||
Some great documentation
|
||
of an amazing
|
||
syntax node
|
||
""",
|
||
children: [
|
||
Child(
|
||
documentation: """
|
||
The one and only child
|
||
\#("")
|
||
"""
|
||
)
|
||
]
|
||
)
|
||
]
|
||
|
||
"""#
|
||
testClient.openDocument(source, uri: uri)
|
||
|
||
let response = try await testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
)
|
||
|
||
let edits = try XCTUnwrap(response)
|
||
let formattedSource = apply(edits: edits, to: source)
|
||
XCTAssert(edits.allSatisfy { $0.newText.allSatisfy(\.isWhitespace) })
|
||
|
||
XCTAssertEqual(
|
||
formattedSource,
|
||
#"""
|
||
_ = [
|
||
Node(
|
||
documentation: """
|
||
Some great documentation
|
||
of an amazing
|
||
syntax node
|
||
""",
|
||
children: [
|
||
Child(
|
||
documentation: """
|
||
The one and only child
|
||
|
||
"""
|
||
)
|
||
]
|
||
)
|
||
]
|
||
|
||
"""#
|
||
)
|
||
}
|
||
|
||
func testSwiftFormatCrashing() async throws {
|
||
try await withTestScratchDir { scratchDir in
|
||
let toolchain = try await unwrap(ToolchainRegistry.forTesting.default)
|
||
|
||
let crashingExecutablePath = scratchDir.appending(component: "crashing-executable")
|
||
try await createBinary(
|
||
"""
|
||
fatalError()
|
||
""",
|
||
at: crashingExecutablePath
|
||
)
|
||
|
||
let toolchainRegistry = ToolchainRegistry(toolchains: [
|
||
Toolchain(
|
||
identifier: "\(toolchain.identifier)-crashing-swift-format",
|
||
displayName: "\(toolchain.identifier) with crashing swift-format",
|
||
path: toolchain.path,
|
||
clang: toolchain.clang,
|
||
swift: toolchain.swift,
|
||
swiftc: toolchain.swiftc,
|
||
swiftFormat: crashingExecutablePath,
|
||
clangd: toolchain.clangd,
|
||
sourcekitd: toolchain.sourcekitd,
|
||
sourceKitClientPlugin: toolchain.sourceKitClientPlugin,
|
||
sourceKitServicePlugin: toolchain.sourceKitServicePlugin,
|
||
libIndexStore: toolchain.libIndexStore
|
||
)
|
||
])
|
||
let testClient = try await TestSourceKitLSPClient(toolchainRegistry: toolchainRegistry)
|
||
let uri = DocumentURI(for: .swift)
|
||
testClient.openDocument(
|
||
// Generate a large source file to increase the chance of the executable we substitute for swift-format
|
||
// crashing before the entire input file is sent to it.
|
||
String(repeating: "func foo() {}\n", count: 10_000),
|
||
uri: uri
|
||
)
|
||
await assertThrowsError(
|
||
try await testClient.send(
|
||
DocumentFormattingRequest(
|
||
textDocument: TextDocumentIdentifier(uri),
|
||
options: FormattingOptions(tabSize: 2, insertSpaces: true)
|
||
)
|
||
),
|
||
expectedMessage: #/Running swift-format failed|Writing to swift-format stdin failed/#
|
||
)
|
||
}
|
||
}
|
||
}
|