//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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 CompletionScoring
import Csourcekitd
@_spi(SourceKitLSP) import LanguageServerProtocol
import SKLogging
import SKTestSupport
import SourceKitD
import SwiftExtensions
import ToolchainRegistry
@_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions
import XCTest

final class SwiftSourceKitPluginTests: SourceKitLSPTestCase {
  /// Returns a path to a file name that is unique to this test execution.
  ///
  /// The file does not actually exist on disk.
  private func scratchFilePath(testName: String = #function, fileName: String = "a.swift") -> String {
    #if os(Windows)
    return "C:\\\(testScratchName(testName: testName))\\\(fileName)"
    #else
    return "/\(testScratchName(testName: testName))/\(fileName)"
    #endif
  }

  func getSourceKitD() async throws -> SourceKitD {
    guard let sourcekitd = await ToolchainRegistry.forTesting.default?.sourcekitd else {
      struct NoSourceKitdFound: Error, CustomStringConvertible {
        var description: String = "Could not find SourceKitD"
      }
      throw NoSourceKitdFound()
    }
    return try await SourceKitD.getOrCreate(
      dylibPath: sourcekitd,
      pluginPaths: try sourceKitPluginPaths
    )
  }

  func testBasicCompletion() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    try await SkipUnless.sourcekitdSupportsFullDocumentationInCompletion()

    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func test() {
            self.1️⃣ 2️⃣
          }
        }
        """,
      compilerArguments: [path]
    )

    await assertThrowsError(
      try await sourcekitd.completeUpdate(path: path, position: positions["1️⃣"], filter: ""),
      expectedMessage: #/no matching session/#
    )

    await assertThrowsError(
      try await sourcekitd.completeClose(path: path, position: positions["1️⃣"]),
      expectedMessage: #/no matching session/#
    )

    func checkTestMethod(result: CompletionResultSet, file: StaticString = #filePath, line: UInt = #line) {
      guard let test = result.items.first(where: { $0.name == "test()" }) else {
        XCTFail("did not find test(); got \(result.items)", file: file, line: line)
        return
      }
      XCTAssertEqual(test.kind, sourcekitd.values.declMethodInstance, file: file, line: line)
      XCTAssertEqual(test.description, "test()", file: file, line: line)
      XCTAssertEqual(test.sourcetext, "test()", file: file, line: line)
      XCTAssertEqual(test.typename, "Void", file: file, line: line)
      XCTAssertEqual(test.priorityBucket, 9, file: file, line: line)
      XCTAssertFalse(test.semanticScore.isNaN, file: file, line: line)
      XCTAssertEqual(test.isSystem, false, file: file, line: line)
      XCTAssertEqual(test.numBytesToErase, 0, file: file, line: line)
      XCTAssertEqual(test.hasDiagnostic, false, file: file, line: line)
    }

    func checkTestMethodAnnotated(result: CompletionResultSet, file: StaticString = #filePath, line: UInt = #line) {
      guard let test = result.items.first(where: { $0.name == "test()" }) else {
        XCTFail("did not find test(); got \(result.items)", file: file, line: line)
        return
      }
      XCTAssertEqual(test.kind, sourcekitd.values.declMethodInstance, file: file, line: line)
      XCTAssertEqual(test.description, "<name>test</name>()", file: file, line: line)
      XCTAssertEqual(test.sourcetext, "test()", file: file, line: line)
      XCTAssertEqual(test.typename, "<typeid.sys>Void</typeid.sys>", file: file, line: line)
      XCTAssertEqual(test.priorityBucket, 9, file: file, line: line)
      XCTAssertFalse(test.semanticScore.isNaN, file: file, line: line)
      XCTAssertEqual(test.isSystem, false, file: file, line: line)
      XCTAssertEqual(test.numBytesToErase, 0, file: file, line: line)
      XCTAssertEqual(test.hasDiagnostic, false, file: file, line: line)
    }

    var unfilteredResultCount: Int? = nil

    let result1 = try await sourcekitd.completeOpen(path: path, position: positions["1️⃣"], filter: "")
    XCTAssertEqual(result1.items.count, result1.unfilteredResultCount)
    checkTestMethod(result: result1)
    unfilteredResultCount = result1.unfilteredResultCount

    let result2 = try await sourcekitd.completeUpdate(path: path, position: positions["1️⃣"], filter: "")
    XCTAssertEqual(result2.items.count, result2.unfilteredResultCount)
    XCTAssertEqual(result2.unfilteredResultCount, unfilteredResultCount)
    checkTestMethod(result: result2)

    let result3 = try await sourcekitd.completeUpdate(path: path, position: positions["1️⃣"], filter: "test")
    XCTAssertEqual(result3.unfilteredResultCount, unfilteredResultCount)
    XCTAssertEqual(result3.items.count, 1)
    checkTestMethod(result: result3)

    let result4 = try await sourcekitd.completeUpdate(path: path, position: positions["1️⃣"], filter: "testify")
    XCTAssertEqual(result4.unfilteredResultCount, unfilteredResultCount)
    XCTAssertEqual(result4.items.count, 0)

    // Update on different location
    await assertThrowsError(
      try await sourcekitd.completeUpdate(path: path, position: positions["2️⃣"], filter: ""),
      expectedMessage: #/no matching session/#
    )
    await assertThrowsError(
      try await sourcekitd.completeClose(path: path, position: positions["2️⃣"]),
      expectedMessage: #/no matching session/#
    )

    // Update on different location
    await assertThrowsError(
      try await sourcekitd.completeUpdate(path: "/other.swift", position: positions["1️⃣"], filter: ""),
      expectedMessage: #/no matching session/#
    )

    // Annotated
    let result5 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      flags: [.annotate]
    )
    XCTAssertEqual(result5.items.count, result5.unfilteredResultCount)
    checkTestMethodAnnotated(result: result5)

    let result6 = try await sourcekitd.completeUpdate(
      path: path,
      position: positions["1️⃣"],
      filter: "test",
      flags: [.annotate]
    )
    XCTAssertEqual(result6.items.count, 1)
    checkTestMethodAnnotated(result: result6)

    try await sourcekitd.completeClose(path: path, position: positions["1️⃣"])

    await assertThrowsError(
      try await sourcekitd.completeUpdate(path: path, position: positions["1️⃣"], filter: ""),
      expectedMessage: #/no matching session/#
    )
  }

  func testEmptyName() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        func test1(closure: () -> Void) {
          closure(1️⃣
        }
        func noArg() -> String {}
        func noArg() -> Int {}
        func test2() {
          noArg(2️⃣
        }
        """,
      compilerArguments: [path]
    )
    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      flags: [.annotate]
    )
    XCTAssertEqual(result1.items.count, 1)
    XCTAssertEqual(result1.items[0].name, "")

    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["2️⃣"],
      filter: "",
      flags: [.annotate],
      maxResults: 1
    )
    XCTAssertEqual(result2.items.count, 1)
    XCTAssertEqual(result2.items[0].name, "")
    let doc = try await sourcekitd.completeDocumentation(id: result2.items[0].id)
    XCTAssertNil(doc.docComment)
    XCTAssertNil(doc.docFullAsXML)
    XCTAssertNil(doc.docBrief)
  }

  func testMultipleFiles() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let pathA = scratchFilePath(fileName: "a.swift")
    let pathB = scratchFilePath(fileName: "b.swift")
    let positionsA = try await sourcekitd.openDocument(
      pathA,
      contents: """
        struct A {
          func aaa(b: B) {
            b.1️⃣
          }
        }
        """,
      compilerArguments: [pathA, pathB]
    )
    let positionsB = try await sourcekitd.openDocument(
      pathB,
      contents: """
        struct B {
          func bbb(a: A) {
            a.2️⃣
          }
        }
        """,
      compilerArguments: [pathA, pathB]
    )

    func checkResult(name: String, result: CompletionResultSet, file: StaticString = #filePath, line: UInt = #line) {
      guard let test = result.items.first(where: { $0.name == name }) else {
        XCTFail("did not find \(name); got \(result.items)", file: file, line: line)
        return
      }
      XCTAssertEqual(test.kind, sourcekitd.values.declMethodInstance, file: file, line: line)
    }

    let result1 = try await sourcekitd.completeOpen(
      path: pathA,
      position: positionsA["1️⃣"],
      filter: ""
    )
    checkResult(name: "bbb(a:)", result: result1)

    let result2 = try await sourcekitd.completeUpdate(
      path: pathA,
      position: positionsA["1️⃣"],
      filter: "b"
    )
    checkResult(name: "bbb(a:)", result: result2)

    let result3 = try await sourcekitd.completeOpen(
      path: pathB,
      position: positionsB["2️⃣"],
      filter: ""
    )
    checkResult(name: "aaa(b:)", result: result3)

    let result4 = try await sourcekitd.completeUpdate(
      path: pathB,
      position: positionsB["2️⃣"],
      filter: "a"
    )
    checkResult(name: "aaa(b:)", result: result4)
  }

  func testCancellation() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct A: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) {} }
        struct B: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) {} }
        struct C: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) {} }

        func + (lhs: A, rhs: B) -> A { fatalError() }
        func + (lhs: B, rhs: C) -> A { fatalError() }
        func + (lhs: C, rhs: A) -> A { fatalError() }

        func + (lhs: B, rhs: A) -> B { fatalError() }
        func + (lhs: C, rhs: B) -> B { fatalError() }
        func + (lhs: A, rhs: C) -> B { fatalError() }

        func + (lhs: C, rhs: B) -> C { fatalError() }
        func + (lhs: B, rhs: C) -> C { fatalError() }
        func + (lhs: A, rhs: A) -> C { fatalError() }

        class Foo {
          func slow(x: Invalid1, y: Invalid2) {
            let x: C = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 1️⃣
          }

          struct Foo {
            let fooMember: String
          }

          func fast(a: Foo) {
            a.2️⃣
          }
        }
        """,
      compilerArguments: [path]
    )

    let slowCompletionRequestSent = self.expectation(description: "slow completion result sent")
    let slowCompletionResultReceived = self.expectation(description: "slow completion")
    try await sourcekitd.withRequestHandlingHook {
      let slowCompletionTask = Task {
        await assertThrowsError(try await sourcekitd.completeOpen(path: path, position: positions["1️⃣"], filter: "")) {
          XCTAssert($0 is CancellationError, "Expected completion to be cancelled, failed with \($0)")
        }
        slowCompletionResultReceived.fulfill()
      }
      // Wait for the slow completion request to actually be sent to sourcekitd. Otherwise, we might hit a cancellation
      // check somewhere during request sending and we aren't actually sending the completion request to sourcekitd.
      try await fulfillmentOfOrThrow(slowCompletionRequestSent)

      slowCompletionTask.cancel()
      try await fulfillmentOfOrThrow(slowCompletionResultReceived, timeout: 30)
    } hook: { request in
      // Check that we aren't matching against a request sent by something else that has handle to the same sourcekitd.
      assertContains(request.description.replacing(#"\\"#, with: #"\"#), path)
      slowCompletionRequestSent.fulfill()
    }

    let fastCompletionStarted = Date()
    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["2️⃣"],
      filter: ""
    )
    XCTAssert(result.items.count > 0)
    XCTAssertLessThan(Date().timeIntervalSince(fastCompletionStarted), 30)
  }

  func testEdits() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func test() {
            let solf = 1
            solo.1️⃣
          }
          func magic_method_of_greatness() {}
        }
        """,
      compilerArguments: [path]
    )

    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "magic_method_of_greatness",
      flags: []
    )
    XCTAssertEqual(result1.unfilteredResultCount, 0)
    XCTAssertEqual(result1.items.count, 0)

    let sOffset = """
      struct S {
        func test() {
          let solo = 1
          s
      """.count - 1

    try await sourcekitd.editDocument(path, fromOffset: sOffset + 1, length: 1, newContents: "e")
    try await sourcekitd.editDocument(path, fromOffset: sOffset + 3, length: 1, newContents: "f")

    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "magic_method_of_greatness",
      flags: []
    )
    XCTAssertGreaterThan(result2.unfilteredResultCount, 1)
    XCTAssertEqual(result2.items.count, 1)

    try await sourcekitd.editDocument(path, fromOffset: sOffset, length: 3, newContents: "")
    try await sourcekitd.editDocument(path, fromOffset: sOffset, length: 0, newContents: "sel")

    let result3 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "magic_method_of_greatness",
      flags: []
    )
    XCTAssertGreaterThan(result3.unfilteredResultCount, 1)
    XCTAssertEqual(result3.items.count, 1)
  }

  func testEditBounds() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    _ = try await sourcekitd.openDocument(
      path,
      contents: "",
      compilerArguments: [path]
    )

    let typeWithMethod = """
      struct S {
        static func foo() -> Int {}
      }

      """
    var fullText = typeWithMethod

    try await sourcekitd.editDocument(path, fromOffset: 0, length: 0, newContents: typeWithMethod)

    let completion = """
      S.
      """
    fullText += completion

    try await sourcekitd.editDocument(path, fromOffset: typeWithMethod.utf8.count, length: 0, newContents: completion)

    func testCompletion(file: StaticString = #filePath, line: UInt = #line) async throws {
      let result = try await sourcekitd.completeOpen(
        path: path,
        position: Position(line: 3, utf16index: 2),
        filter: "foo",
        flags: []
      )
      XCTAssertGreaterThan(result.unfilteredResultCount, 1, file: file, line: line)
      XCTAssertEqual(result.items.count, 1, file: file, line: line)
    }
    try await testCompletion()

    // Bogus edits are ignored (negative offsets crash SourceKit itself so we don't test them here).
    await assertThrowsError(
      try await sourcekitd.editDocument(path, fromOffset: 0, length: 99999, newContents: "")
    )
    await assertThrowsError(
      try await sourcekitd.editDocument(path, fromOffset: 99999, length: 1, newContents: "")
    )
    await assertThrowsError(
      try await sourcekitd.editDocument(path, fromOffset: 99999, length: 0, newContents: "unrelated")
    )
    // SourceKit doesn't throw an error for a no-op edit.
    try await sourcekitd.editDocument(path, fromOffset: 99999, length: 0, newContents: "")

    try await sourcekitd.editDocument(path, fromOffset: 0, length: 0, newContents: "")
    try await sourcekitd.editDocument(path, fromOffset: fullText.utf8.count, length: 0, newContents: "")

    try await testCompletion()

    let badCompletion = """
      X.
      """
    fullText = fullText.dropLast(2) + badCompletion

    try await sourcekitd.editDocument(path, fromOffset: fullText.utf8.count - 2, length: 2, newContents: badCompletion)

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: Position(line: 3, utf16index: 2),
      filter: "foo",
      flags: []
    )
    XCTAssertEqual(result.unfilteredResultCount, 0)
    XCTAssertEqual(result.items.count, 0)
  }

  func testDocumentation() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    try await SkipUnless.sourcekitdSupportsFullDocumentationInCompletion()

    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        protocol P {
          /// Protocol P foo1
          func foo1()
        }
        struct S: P {
          func foo1() {}
          /// Struct S foo2
          func foo2() {}
          func foo3() {}
          func test() {
            self.1️⃣
          }
        }
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "foo"
    )
    XCTAssertGreaterThan(result.unfilteredResultCount, 3)
    let sym1 = try unwrap(result.items.first(where: { $0.name == "foo1()" }), "did not find foo1; got \(result.items)")
    let sym2 = try unwrap(result.items.first(where: { $0.name == "foo2()" }), "did not find foo2; got \(result.items)")
    let sym3 = try unwrap(result.items.first(where: { $0.name == "foo3()" }), "did not find foo3; got \(result.items)")

    let sym1Doc = try await sourcekitd.completeDocumentation(id: sym1.id)
    XCTAssertEqual(sym1Doc.docComment, "Protocol P foo1")
    XCTAssertEqual(
      sym1Doc.docFullAsXML,
      """
      <Function file="\(path)" line="3" column="8">\
      <Name>foo1()</Name>\
      <USR>s:1a1PP4foo1yyF</USR>\
      <Declaration>func foo1()</Declaration>\
      <CommentParts>\
      <Abstract><Para>Protocol P foo1</Para></Abstract>\
      <Discussion><Note>\
      <Para>This documentation comment was inherited from <codeVoice>P</codeVoice>.</Para>\
      </Note></Discussion>\
      </CommentParts>\
      </Function>
      """
    )
    XCTAssertEqual(sym1Doc.docBrief, "Protocol P foo1")
    XCTAssertEqual(sym1Doc.associatedUSRs, ["s:1a1SV4foo1yyF", "s:1a1PP4foo1yyF"])

    let sym2Doc = try await sourcekitd.completeDocumentation(id: sym2.id)
    XCTAssertEqual(sym2Doc.docComment, "Struct S foo2")
    XCTAssertEqual(
      sym2Doc.docFullAsXML,
      """
      <Function file="\(path)" line="8" column="8">\
      <Name>foo2()</Name>\
      <USR>s:1a1SV4foo2yyF</USR>\
      <Declaration>func foo2()</Declaration>\
      <CommentParts>\
      <Abstract><Para>Struct S foo2</Para></Abstract>\
      </CommentParts>\
      </Function>
      """
    )
    XCTAssertEqual(sym2Doc.docBrief, "Struct S foo2")
    XCTAssertEqual(sym2Doc.associatedUSRs, ["s:1a1SV4foo2yyF"])

    let sym3Doc = try await sourcekitd.completeDocumentation(id: sym3.id)
    XCTAssertNil(sym3Doc.docComment)
    XCTAssertNil(sym3Doc.docFullAsXML)
    XCTAssertNil(sym3Doc.docBrief)
    XCTAssertEqual(sym3Doc.associatedUSRs, ["s:1a1SV4foo3yyF"])
  }

  func testNumBytesToErase() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S { var myVar: Int }
        func test(s: S?) {
          s.1️⃣
        }
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    XCTAssertEqual(result.items.count, result.unfilteredResultCount)
    let myVar = try unwrap(result.items.first(where: { $0.name == "myVar" }), "did not find myVar; got \(result.items)")
    XCTAssertEqual(myVar.isSystem, false)
    XCTAssertEqual(myVar.numBytesToErase, 1)

    let unwrapped = try unwrap(
      result.items.first(where: { $0.name == "unsafelyUnwrapped" }),
      "did not find myVar; got \(result.items)"
    )

    XCTAssertEqual(unwrapped.isSystem, true)
    XCTAssertEqual(unwrapped.numBytesToErase, 0)
  }

  func testObjectLiterals() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        func test() {
        }1️⃣
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    XCTAssertFalse(
      result.items.contains(where: {
        $0.description.hasPrefix("#colorLiteral") || $0.description.hasPrefix("#imageLiteral")
      })
    )
  }

  func testAddInitsToTopLevel() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        func test() {
        1️⃣}
        struct MyStruct {
        init(arg1: Int) {}
        init(arg2: String) {}
        }
        """,
      compilerArguments: [path]
    )

    // With 'addInitsToTopLevel'
    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "MyStr",
      flags: [.addInitsToTopLevel]
    )

    XCTAssert(result1.items.filter({ $0.description.hasPrefix("MyStruct") }).count == 3)
    let typeResult = try unwrap(
      result1.items.first { $0.description == "MyStruct" && $0.kind == sourcekitd.values.declStruct }
    )
    XCTAssertNotNil(typeResult.groupID)
    XCTAssert(
      result1.items.contains(where: {
        $0.description.hasPrefix("MyStruct(arg1:") && $0.kind == sourcekitd.values.declConstructor
          && $0.groupID == typeResult.groupID
      })
    )
    XCTAssert(
      result1.items.contains(where: {
        $0.description.hasPrefix("MyStruct(arg2:") && $0.kind == sourcekitd.values.declConstructor
          && $0.groupID == typeResult.groupID
      })
    )
    XCTAssertLessThan(
      try unwrap(result1.items.firstIndex(where: { $0.description == "MyStruct" })),
      try unwrap(result1.items.firstIndex(where: { $0.description.hasPrefix("MyStruct(") })),
      "Type names must precede the initializer calls"
    )

    // Without 'addInitsToTopLevel'
    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "MyStr",
      flags: []
    )
    XCTAssert(result2.items.filter({ $0.description.hasPrefix("MyStruct") }).count == 1)
    XCTAssertFalse(result2.items.contains(where: { $0.description.hasPrefix("MyStruct(") }))
  }

  func testMembersGroupID() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct Animal {
          var name: String
          var species: String
          func name(changedTo: String) { }
          func name(updatedTo: String) { }
          func otherFunction() { }
        }
        func test() {
          let animal = Animal(name: "", species: "")
          animal.1️⃣
        }
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    guard result.items.count == 6 else {
      XCTFail("Expected 6 completion results; received \(result)")
      return
    }

    // Properties don't have a groupID.
    XCTAssertEqual(result.items[0].name, "name")
    XCTAssertNil(result.items[0].groupID)
    XCTAssertEqual(result.items[1].name, "species")
    XCTAssertNil(result.items[1].groupID)

    XCTAssertEqual(result.items[2].name, "name(changedTo:)")
    XCTAssertEqual(result.items[3].name, "name(updatedTo:)")
    XCTAssertEqual(result.items[2].groupID, result.items[3].groupID)

    XCTAssertEqual(result.items[4].name, "otherFunction()")
    XCTAssertNotNil(result.items[4].groupID)
  }

  func testAddCallWithNoDefaultArgs() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct Defaults {
          func noDefault(a: Int) { }
          func singleDefault(a: Int = 0) { }
        }
        func defaults(def: Defaults) {
          def.1️⃣
        }
        """,
      compilerArguments: [path]
    )

    // With 'addCallWithNoDefaultArgs'
    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      flags: [.addCallWithNoDefaultArgs]
    )
    guard result1.items.count == 4 else {
      XCTFail("Expected 4 results; received \(result1)")
      return
    }
    XCTAssertEqual(result1.items[0].description, "noDefault(a: Int)")
    XCTAssertEqual(result1.items[1].description, "singleDefault()")
    XCTAssertEqual(result1.items[2].description, "singleDefault(a: Int)")
    XCTAssertEqual(result1.items[3].description, "self")

    // Without 'addCallWithNoDefaultArgs'
    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    guard result2.items.count == 3 else {
      XCTFail("Expected 3 results; received \(result2)")
      return
    }
    XCTAssertEqual(result2.items[0].description, "noDefault(a: Int)")
    XCTAssertEqual(result2.items[1].description, "singleDefault(a: Int)")
    XCTAssertEqual(result2.items[2].description, "self")
  }

  func testTextMatchScore() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func goodMatchOne() {}
          func goodMatchNotOneButTwo() {}
          func test() {
            self.1️⃣
          }
        }
        """,
      compilerArguments: [path]
    )

    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "gmo"
    )
    XCTAssertGreaterThan(result1.unfilteredResultCount, result1.items.count)
    guard result1.items.count >= 2 else {
      XCTFail("Expected at least 2 results; received \(result1)")
      return
    }
    XCTAssertEqual(result1.items[0].description, "goodMatchOne()")
    XCTAssertEqual(result1.items[1].description, "goodMatchNotOneButTwo()")
    XCTAssertGreaterThan(result1.items[0].textMatchScore, result1.items[1].textMatchScore)
    let result1Score = result1.items[0].textMatchScore

    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "gmo",
      useXPC: true
    )
    guard result2.items.count >= 2 else {
      XCTFail("Expected at least 2 results; received \(result2)")
      return
    }
    XCTAssertEqual(result2.items[0].description, "goodMatchOne()")
    XCTAssertEqual(result2.items[1].description, "goodMatchNotOneButTwo()")
    XCTAssertGreaterThan(result2.items[0].textMatchScore, result2.items[1].textMatchScore)
    XCTAssertEqual(result2.items[0].textMatchScore, result1Score)
  }

  func testSemanticScore() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func goodMatchAsync() async {}
          @available(*, deprecated)
          func goodMatchDeprecated() {}
          func goodMatchType() {}
          func test() {
            let goodMatchLocal = 1
        1️⃣
          }
        }
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "goodMatch"
    )
    guard result.items.count >= 4 else {
      XCTFail("Expected at least 4 results; received \(result)")
      return
    }
    XCTAssertEqual(result.items[0].description, "goodMatchLocal")
    XCTAssertEqual(result.items[1].description, "goodMatchAsync() async")
    XCTAssertEqual(result.items[2].description, "goodMatchType()")
    XCTAssertEqual(result.items[3].description, "goodMatchDeprecated()")
    XCTAssertGreaterThan(result.items[0].semanticScore, result.items[1].semanticScore)
    // Note: async and deprecated get the same penalty currently, but we don't want to be too specific in this test.
    XCTAssertEqual(result.items[1].semanticScore, result.items[2].semanticScore)
    XCTAssertGreaterThan(result.items[1].semanticScore, result.items[3].semanticScore)
    XCTAssertFalse(result.items[1].hasDiagnostic)
    XCTAssertFalse(result.items[2].hasDiagnostic)
    XCTAssertTrue(result.items[3].hasDiagnostic)
  }

  func testSemanticScoreInit() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        enum E {
          case good
          init(_ param: Int, param2: String) { self = .good }
          func test() {
            let _: E = .1️⃣
          }
          func test2() {
            let local = 1
            E(2️⃣)
          }
        }
        """,
      compilerArguments: [path]
    )

    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    guard result1.items.count >= 2 else {
      XCTFail("Expected at least 2 results; received \(result1)")
      return
    }
    XCTAssertEqual(result1.items[0].description, "good")
    XCTAssertEqual(result1.items[1].description, "init(param: Int, param2: String)")
    XCTAssertGreaterThan(result1.items[0].semanticScore, result1.items[1].semanticScore)

    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["2️⃣"],
      filter: ""
    )
    guard result2.items.count >= 2 else {
      XCTFail("Expected at least 2 results; received \(result2)")
      return
    }
    XCTAssertEqual(result2.items[0].description, "(param: Int, param2: String)")
    XCTAssertEqual(result2.items[1].description, "local")
    XCTAssertGreaterThan(result2.items[0].semanticScore, result2.items[1].semanticScore)
  }

  func testSemanticScoreComponents() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct Animal {
          var name = "Test"
          func breed() { }
        }
        let animal = Animal()
        animal.1️⃣
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      flags: [.includeSemanticComponents]
    )

    XCTAssertEqual(result.items.count, 3)
    for item in result.items {
      let data = Data(base64Encoded: try unwrap(item.semanticScoreComponents))!
      let bytes = [UInt8](data)
      XCTAssertFalse(bytes.isEmpty)
      let classification = try SemanticClassification(byteRepresentation: bytes)
      XCTAssertEqual(classification.score, item.semanticScore)
    }
  }

  func testMemberAccessTypes() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath(fileName: "AnimalKit.swift")
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        class Animal { }
        class Dog: Animal {
          var name = "Test"
          func breed() { }
        }
        let dog = Dog()
        dog1️⃣.
        """,
      compilerArguments: [path]
    )

    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    XCTAssertEqual(result1.memberAccessTypes, ["AnimalKit.Dog", "AnimalKit.Animal"])
    guard result1.items.count == 5 else {
      XCTFail("Expected 5 result. Received \(result1)")
      return
    }
    XCTAssertEqual(result1.items[0].module, "AnimalKit")
    XCTAssertEqual(result1.items[0].name, "name")
    XCTAssertEqual(result1.items[1].module, "AnimalKit")
    XCTAssertEqual(result1.items[1].name, "breed()")

    let result2 = try await sourcekitd.completeUpdate(
      path: path,
      position: positions["1️⃣"],
      filter: "name"
    )
    XCTAssertEqual(result2.memberAccessTypes, ["AnimalKit.Dog", "AnimalKit.Animal"])
    guard result2.items.count == 1 else {
      XCTFail("Expected 1 result. Received \(result2)")
      return
    }
    XCTAssertEqual(result2.items[0].module, "AnimalKit")
    XCTAssertEqual(result2.items[0].name, "name")
  }

  func testTypeModule() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath(fileName: "AnimalKit.swift")
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        class Animal { }
        class Dog: Animal {
          var name = "Test"
          func breed() { }
        }
        AnimalKit1️⃣.
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    XCTAssertEqual(result.memberAccessTypes, [])
    XCTAssertEqual(result.items.count, 2)
    // Note: the order of `Animal` and `Dog` isn't stable.
    for item in result.items {
      XCTAssertEqual(item.module, "AnimalKit")
    }
  }

  func testKeyword() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        1️⃣
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "extensio"
    )
    XCTAssertEqual(result.memberAccessTypes, [])
    guard result.items.count == 1 else {
      XCTFail("Expected 1 result. Received \(result)")
      return
    }
    XCTAssertEqual(result.items[0].name, "extension")
    XCTAssertNil(result.items[0].module)
  }

  func testSemanticScoreComponentsAsExtraUpdate() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func test() {
            self.1️⃣
          }
        }
        """,
      compilerArguments: [path]
    )

    // Open without `includeSemanticComponents`.
    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    XCTAssertEqual(result1.items.count, 2)
    for item in result1.items {
      XCTAssertNil(item.semanticScoreComponents)
    }

    // Update without `includeSemanticComponents`.
    let result2 = try await sourcekitd.completeUpdate(
      path: path,
      position: positions["1️⃣"],
      filter: "t"
    )
    XCTAssertEqual(result2.items.count, 1)
    for item in result2.items {
      XCTAssertNil(item.semanticScoreComponents)
    }

    // Now, do the same update _with_ `includeSemanticComponents`.
    let result3 = try await sourcekitd.completeUpdate(
      path: path,
      position: positions["1️⃣"],
      filter: "t",
      flags: [.includeSemanticComponents]
    )
    XCTAssertEqual(result3.items.count, 1)
    for item in result3.items {
      // Assert we get `semanticScoreComponents`,
      // when `update` is called with different options than `open`.
      XCTAssertNotNil(item.semanticScoreComponents)
    }

    // Same update _without_ `includeSemanticComponents`.
    let result4 = try await sourcekitd.completeUpdate(
      path: path,
      position: positions["1️⃣"],
      filter: "t"
    )
    XCTAssertEqual(result4.items.count, 1)
    for item in result4.items {
      // Response no longer contains the `semanticScoreComponents`.
      XCTAssertNil(item.semanticScoreComponents)
    }
  }

  // rdar://104381080 (NSImage(imageLiteralResourceName:) was my top completion — this seems odd)
  func testPopularityForTypeFromSubmodule() async throws {
    #if !os(macOS)
    try XCTSkipIf(true, "AppKit is only defined on macOS")
    #endif
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        import AppKit
        func test() {
          1️⃣
        }
        """,
      compilerArguments: [path]
    )

    let popularityIndex = """
      {
        "AppKit": {
          "scores": [
            0.6
          ],
          "values": [
            "NSImage"
          ]
        }
      }
      """
    try await withTestScratchDir { scratchDir in
      let popularityIndexPath = scratchDir.appending(component: "popularityIndex.json")
      try popularityIndex.write(to: popularityIndexPath, atomically: true, encoding: .utf8)
      try await sourcekitd.setPopularityIndex(
        scopedPopularityDataPath: try popularityIndexPath.filePath,
        popularModules: [],
        notoriousModules: []
      )

      let result = try await sourcekitd.completeOpen(
        path: path,
        position: positions["1️⃣"],
        filter: "",
        flags: [.addInitsToTopLevel]
      )
      // `NSImage` is defined in `AppKit.NSImage` (a submodule).
      // The popularity index is keyed only on the base module (e.g. `AppKit`).
      // This asserts we correctly see `NSImage` as popular.
      XCTAssertEqual(result.items.first?.description, "NSImage")
    }
  }

  func testPopularity() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func test() {
            self.1️⃣
          }
          let popular: Int = 0
          let other1: Int = 0
          let unpopular: Int = 0
          let other2: Int = 0
          let recent1: Int = 0
          let recent2: Int = 0
          let other3: Int = 0
        }
        """,
      compilerArguments: [path]
    )

    // Reset the scoped popularity data path if it was set by previous requests
    try await sourcekitd.setPopularityIndex(
      scopedPopularityDataPath: "/invalid",
      popularModules: [],
      notoriousModules: []
    )

    try await sourcekitd.setPopularAPI(popular: ["popular"], unpopular: ["unpopular"])

    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      recentCompletions: ["recent1"]
    )
    guard result1.items.count >= 6 else {
      XCTFail("Expected at least 6 results. Received \(result1)")
      return
    }
    XCTAssertEqual(result1.items[0].description, "popular")
    XCTAssertEqual(result1.items[1].description, "recent1")
    XCTAssertEqual(result1.items[2].description, "other1")
    XCTAssertEqual(result1.items.last?.description, "unpopular")

    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      recentCompletions: ["recent2", "recent1"]
    )

    guard result2.items.count >= 6 else {
      XCTFail("Expected at least 6 results. Received \(result2)")
      return
    }
    XCTAssertEqual(result2.items[0].description, "popular")
    XCTAssertEqual(result2.items[1].description, "recent2")
    XCTAssertEqual(result2.items[2].description, "recent1")
    XCTAssertEqual(result2.items[3].description, "other1")
    XCTAssertEqual(result2.items.last?.description, "unpopular")

    try await sourcekitd.setPopularAPI(popular: [], unpopular: [])

    let result3 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      recentCompletions: ["recent2", "recent1"]
    )
    guard result3.items.count >= 6 else {
      XCTFail("Expected at least 6 results. Received \(result3)")
      return
    }
    XCTAssertEqual(result3.items[0].description, "recent2")
    XCTAssertEqual(result3.items[1].description, "recent1")
    // Results 2 - 6 share the same score
    XCTAssertEqual(result3.items[2].description, "popular")
    XCTAssertEqual(result3.items[3].description, "other1")
    XCTAssertEqual(result3.items[4].description, "unpopular")
    XCTAssertEqual(result3.items[5].description, "other2")
    XCTAssertEqual(result3.items[6].description, "other3")
    XCTAssertEqual(result3.items[2].semanticScore, result3.items[3].semanticScore)
    XCTAssertEqual(result3.items[2].semanticScore, result3.items[4].semanticScore)
    XCTAssertEqual(result3.items[2].semanticScore, result3.items[5].semanticScore)
    XCTAssertEqual(result3.items[2].semanticScore, result3.items[6].semanticScore)
  }

  func testScopedPopularity() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct Strinz: Encodable {
          var aProp: String = ""
          var value: Int = 12
        }
        func test(arg: Strinz) {
          arg.1️⃣
        }
        func testGlobal() {
          2️⃣
        }
        """,
      compilerArguments: [path, "-module-name", "MyMod"]
    )

    let popularityIndex = """
      {
        "Swift.Encodable": {
          "values": [
            "encode"
          ],
          "scores": [
            1.0
          ]
        }
      }
      """

    try await withTestScratchDir { scratchDir in
      let popularityIndexPath = scratchDir.appending(component: "popularityIndex.json")
      try popularityIndex.write(to: popularityIndexPath, atomically: true, encoding: .utf8)

      let result1 = try await sourcekitd.completeOpen(
        path: path,
        position: positions["1️⃣"],
        filter: ""
      )
      let scoreWithoutPopularity = try XCTUnwrap(result1.items.first(where: { $0.name == "encode(to:)" })).semanticScore

      try await sourcekitd.setPopularityIndex(
        scopedPopularityDataPath: try popularityIndexPath.filePath,
        popularModules: [],
        notoriousModules: ["MyMod"]
      )

      let result2 = try await sourcekitd.completeOpen(
        path: path,
        position: positions["1️⃣"],
        filter: ""
      )
      let scoreWithPopularity = try XCTUnwrap(result2.items.first(where: { $0.name == "encode(to:)" })).semanticScore

      XCTAssert(scoreWithoutPopularity < scoreWithPopularity)

      // Ensure 'notoriousModules' lowers the score.
      let result3 = try await sourcekitd.completeOpen(
        path: path,
        position: positions["2️⃣"],
        filter: "Strin",
        recentCompletions: []
      )
      let string = try XCTUnwrap(result3.items.first(where: { $0.name == "String" }))
      let strinz = try XCTUnwrap(result3.items.first(where: { $0.name == "Strinz" }))
      XCTAssert(string.textMatchScore == strinz.textMatchScore)
      XCTAssert(string.semanticScore > strinz.semanticScore)
    }
  }

  func testModulePopularity() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func test() {
            self.1️⃣
          }
          let foo: Int = 0
        }
        """,
      compilerArguments: [path]
    )

    try await sourcekitd.setPopularityTable(
      PopularityTable(moduleSymbolReferenceTables: [], recentCompletions: [], popularModules: [], notoriousModules: [])
    )

    let result1 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      recentCompletions: []
    )
    let noPopularModulesScore = try unwrap(result1.items.first(where: { $0.description == "foo" })?.semanticScore)

    try await sourcekitd.setPopularityTable(
      PopularityTable(
        moduleSymbolReferenceTables: [],
        recentCompletions: [],
        popularModules: ["a"],
        notoriousModules: []
      )
    )

    let result2 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      recentCompletions: []
    )
    let moduleIsPopularScore = try unwrap(result2.items.first(where: { $0.description == "foo" })?.semanticScore)

    try await sourcekitd.setPopularityTable(
      PopularityTable(
        moduleSymbolReferenceTables: [],
        recentCompletions: [],
        popularModules: [],
        notoriousModules: ["a"]
      )
    )

    let result3 = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: "",
      recentCompletions: []
    )
    let moduleIsUnpopularScore = try unwrap(result3.items.first(where: { $0.description == "foo" })?.semanticScore)

    XCTAssertLessThan(moduleIsUnpopularScore, noPopularModulesScore)
    XCTAssertLessThan(noPopularModulesScore, moduleIsPopularScore)
  }

  func testFlair() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        struct S {
          func foo(x: Int, y: Int) {}
          func foo(_ arg: String) {}
          func test(localArg: String) {
            self.foo(1️⃣)
          }
        }
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(
      path: path,
      position: positions["1️⃣"],
      filter: ""
    )
    guard result.items.count >= 3 else {
      XCTFail("Expected at least 3 results. Received \(result)")
      return
    }
    XCTAssertTrue(Set(result.items[0...1].map(\.description)) == ["(arg: String)", "(x: Int, y: Int)"])
    XCTAssertTrue(result.items[2...].contains(where: { $0.description == "localArg" }))
  }

  func testPluginFilterAndSortPerfAllMatch() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let (position, recent) = try await sourcekitd.perfTestSetup(path: path)

    let initResult = try await sourcekitd.completeOpen(
      path: path,
      position: position,
      filter: "",
      recentCompletions: recent
    )
    XCTAssertEqual(initResult.items.count, 200)

    self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
      assertNoThrow {
        self.startMeasuring()
        let result = try runAsync {
          return try await sourcekitd.completeUpdate(
            path: path,
            position: position,
            filter: ""
          )
        }
        self.stopMeasuring()
        XCTAssertEqual(result.items.count, 200)

        try runAsync {
          // Use a non-matching search to ensure we aren't caching the results.
          let resetResult = try await sourcekitd.completeUpdate(
            path: path,
            position: position,
            filter: "sadfasdfasd"
          )
          XCTAssertEqual(resetResult.items.count, 0)
        }
      }
    }
  }

  func testPluginFilterAndSortPerfFiltered() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let (position, recent) = try await sourcekitd.perfTestSetup(path: path)

    let initResult = try await sourcekitd.completeOpen(
      path: path,
      position: position,
      filter: "",
      recentCompletions: recent
    )
    XCTAssertGreaterThanOrEqual(initResult.unfilteredResultCount, initResult.items.count)

    self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
      assertNoThrow {
        self.startMeasuring()
        let result = try runAsync {
          try await sourcekitd.completeUpdate(
            path: path,
            position: position,
            filter: "mMethS"
          )
        }
        self.stopMeasuring()
        XCTAssertEqual(result.items.count, 200)

        try runAsync {
          // Use a non-matching search to ensure we aren't caching the results.
          let resetResult = try await sourcekitd.completeUpdate(
            path: path,
            position: position,
            filter: "sadfasdfasd"
          )
          XCTAssertEqual(resetResult.items.count, 0)
        }
      }
    }
  }

  func testCrossModuleCompletion() async throws {
    let project = try await PluginSwiftPMTestProject(files: [
      "Sources/LibA/LibA.swift": """
      public struct LibA {
          public init() {}
          public func method() {}
      }
      """,
      "Sources/LibB/LibB.swift": """
      import LibA
      func test(lib: LibA) {
          lib.1️⃣method()
      }
      """,
      "Package.swift": """
      // swift-tools-version: 5.7

      import PackageDescription

      let package = Package(
        name: "MyLibrary",
        targets: [
          .target(name: "LibA"),
          .target(name: "LibB", dependencies: ["LibA"]),
        ]
      )
      """,
    ])

    // Open document in sourcekitd
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let libBPath = try project.uri(for: "LibB.swift").pseudoPath
    try await sourcekitd.openDocument(
      libBPath,
      contents: project.contents(of: "LibB.swift"),
      compilerArguments: project.compilerArguments(for: "LibB.swift")
    )

    // Invoke first code completion
    let result = try await sourcekitd.completeOpen(
      path: libBPath,
      position: project.position(of: "1️⃣", in: "LibB.swift"),
      filter: "met"
    )
    XCTAssertEqual(1, result.items.count)
    XCTAssertEqual(result.items.first?.name, "method()")

    // Modify LibA.swift to contain another memeber on the `LibA` struct
    let modifiedLibA = """
      \(try project.contents(of: "LibA.swift"))
      extension LibA {
        public var meta: Int { 0 }
      }
      """
    try modifiedLibA.write(to: project.uri(for: "LibA.swift").fileURL!, atomically: true, encoding: .utf8)
    try await SwiftPMTestProject.build(at: project.scratchDirectory)

    // Tell sourcekitd that dependencies have been updated and run completion again.
    try await sourcekitd.dependencyUpdated()
    let result2 = try await sourcekitd.completeOpen(
      path: libBPath,
      position: project.position(of: "1️⃣", in: "LibB.swift"),
      filter: "met"
    )
    XCTAssertEqual(Set(result2.items.map(\.name)), ["meta", "method()"])
  }

  func testCompletionImportDepth() async throws {
    let project = try await PluginSwiftPMTestProject(files: [
      "Sources/Main/Main.swift": """
      import Depth1Module

      struct Depth0Struct {}

      func test() {
          1️⃣
          return
      }
      """,
      "Sources/Depth1Module/Depth1.swift": """
      @_exported import Depth2Module
      public struct Depth1Struct {}
      """,
      "Sources/Depth2Module/Depth2.swift": """
      public struct Depth2Struct {}
      """,
      "Package.swift": """
      // swift-tools-version: 5.7

      import PackageDescription

      let package = Package(
        name: "MyLibrary",
        targets: [
          .executableTarget(name: "Main", dependencies: ["Depth1Module"]),
          .target(name: "Depth1Module", dependencies: ["Depth2Module"]),
          .target(name: "Depth2Module"),
        ]
      )
      """,
    ])

    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let mainPath = try project.uri(for: "Main.swift").pseudoPath
    try await sourcekitd.openDocument(
      mainPath,
      contents: project.contents(of: "Main.swift"),
      compilerArguments: project.compilerArguments(for: "Main.swift")
    )

    let result = try await sourcekitd.completeOpen(
      path: mainPath,
      position: project.position(of: "1️⃣", in: "Main.swift"),
      filter: "depth"
    )

    let depth0struct = try unwrap(result.items.first(where: { $0.name == "Depth0Struct" }))
    let depth1struct = try unwrap(result.items.first(where: { $0.name == "Depth1Struct" }))
    let depth2struct = try unwrap(result.items.first(where: { $0.name == "Depth2Struct" }))
    let depth1module = try unwrap(result.items.first(where: { $0.name == "Depth1Module" }))
    let depth2module = try unwrap(result.items.first(where: { $0.name == "Depth2Module" }))

    XCTAssertGreaterThan(depth0struct.semanticScore, depth1struct.semanticScore)
    XCTAssertGreaterThan(depth1struct.semanticScore, depth2struct.semanticScore)

    // Since "module" entry doesn't have "import depth", we only checks that modules are de-prioritized.
    XCTAssertGreaterThan(depth2struct.semanticScore, depth1module.semanticScore)
    XCTAssertGreaterThan(depth2struct.semanticScore, depth2module.semanticScore)
  }

  func testCompletionDiagnostics() async throws {
    #if !os(macOS)
    try XCTSkipIf(true, "Soft deprecation is only defined for macOS in this test case")
    #endif
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        import 1️⃣Swift
        struct S: P {
          @available(*, deprecated)
          func deprecatedF() {}
          func test() {
            self.2️⃣
          }
          var theVariable: Int {
        3️⃣
          }
          @available(macOS, deprecated: 100000.0)
          func softDeprecatedF() {}
        }
        """,
      compilerArguments: [path]
    )

    let result1 = try await sourcekitd.completeOpen(path: path, position: positions["1️⃣"], filter: "Swift")
    let swiftResult = try unwrap(result1.items.filter({ $0.description == "Swift" }).first)
    XCTAssertEqual(swiftResult.description, "Swift")
    XCTAssertEqual(swiftResult.hasDiagnostic, true)

    let diag1 = try unwrap(try await sourcekitd.completeDiagnostic(id: swiftResult.id))

    XCTAssertEqual(diag1.severity, sourcekitd.values.diagWarning)
    XCTAssertEqual(diag1.description, "module 'Swift' is already imported")

    let result2 = try await sourcekitd.completeOpen(path: path, position: positions["2️⃣"], filter: "deprecatedF")
    guard result2.items.count >= 2 else {
      XCTFail("Expected at least 2 results. Received \(result2)")
      return
    }

    XCTAssertEqual(result2.items[0].description, "deprecatedF()")
    XCTAssertEqual(result2.items[0].hasDiagnostic, true)
    let diag2_0 = try unwrap(try await sourcekitd.completeDiagnostic(id: result2.items[0].id))
    XCTAssertEqual(diag2_0.severity, sourcekitd.values.diagWarning)
    XCTAssertEqual(diag2_0.description, "'deprecatedF()' is deprecated")

    XCTAssertEqual(result2.items[1].description, "softDeprecatedF()")
    XCTAssertEqual(result2.items[1].hasDiagnostic, true)
    let diag2_1 = try unwrap(try await sourcekitd.completeDiagnostic(id: result2.items[1].id))
    XCTAssertEqual(diag2_1.severity, sourcekitd.values.diagWarning)
    XCTAssertEqual(diag2_1.description, "'softDeprecatedF()' will be deprecated in a future version of macOS")

    let result4 = try await sourcekitd.completeOpen(path: path, position: positions["3️⃣"], filter: "theVariable")
    guard result4.items.count >= 1 else {
      XCTFail("Expected at least 1 results. Received \(result4)")
      return
    }
    XCTAssertEqual(result4.items[0].description, "theVariable")
    XCTAssertEqual(result4.items[0].hasDiagnostic, true)

    let diag4_0 = try unwrap(try await sourcekitd.completeDiagnostic(id: result4.items[0].id))

    XCTAssertEqual(diag4_0.severity, sourcekitd.values.diagWarning)
    XCTAssertEqual(diag4_0.description, "attempting to access 'theVariable' within its own getter")
  }

  func testActorKind() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        actor MyActor {
        }
        func test() {
        1️⃣}
        """,
      compilerArguments: [path]
    )

    let result = try await sourcekitd.completeOpen(path: path, position: positions["1️⃣"], filter: "My")
    let actorItem = try unwrap(result.items.first { item in item.description == "MyActor" })
    XCTAssertEqual(actorItem.kind, sourcekitd.values.declActor)
  }

  func testMacroKind() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    let positions = try await sourcekitd.openDocument(
      path,
      contents: """
        @attached(conformance) public macro MyConformance() =  #externalMacro(module: "MyMacros", type: "MyConformanceMacro")
        @freestanding(expression) public macro MyExpression(_: Any) -> String = #externalMacro(module: "MyMacros", type: "MyExpressionMacro")

        func testAttached() {
        @1️⃣
        }
        func testFreestanding() {
        _ = #2️⃣
        }
        """,
      compilerArguments: [path]
    )

    let result1 = try await sourcekitd.completeOpen(path: path, position: positions["1️⃣"], filter: "My")
    let macroItem1 = try unwrap(result1.items.first { item in item.name == "MyConformance" })
    XCTAssertEqual(macroItem1.kind, sourcekitd.values.declMacro)

    let result2 = try await sourcekitd.completeOpen(path: path, position: positions["2️⃣"], filter: "My")
    let macroItem2 = try unwrap(result2.items.first { item in item.name.starts(with: "MyExpression") })
    XCTAssertEqual(macroItem2.kind, sourcekitd.values.declMacro)

  }

  func testMaxResults() async throws {
    try await SkipUnless.sourcekitdSupportsPlugin()
    let sourcekitd = try await getSourceKitD()
    let path = scratchFilePath()
    var sourceText = "//dummy\n";
    for i in 0..<200 {
      /// Create at least 200 (default maxResults) items
      sourceText += """
        func foo\(i)() {}

        """
    }
    try await sourcekitd.openDocument(path, contents: sourceText, compilerArguments: [path])

    let position = Position(line: 200, utf16index: 0)

    let result1 = try await sourcekitd.completeOpen(path: path, position: position, filter: "f", maxResults: 3)
    XCTAssertEqual(result1.items.count, 3)

    let result2 = try await sourcekitd.completeUpdate(path: path, position: position, filter: "fo", maxResults: 5)
    XCTAssertEqual(result2.items.count, 5)

    let result3 = try await sourcekitd.completeUpdate(path: path, position: position, filter: "f")
    XCTAssertEqual(result3.items.count, 200)
  }
}

// MARK: - Structured result types

private struct CompletionResultSet: Sendable {
  var unfilteredResultCount: Int
  var memberAccessTypes: [String]
  var items: [CompletionResult]

  init(_ dict: SKDResponseDictionary) throws {
    let keys = dict.sourcekitd.keys
    guard let unfilteredResultCount: Int = dict[keys.unfilteredResultCount],

      let memberAccessTypes = dict[keys.memberAccessTypes]?.asStringArray,
      let results: SKDResponseArray = dict[keys.results]
    else {
      throw TestError(
        "expected {key.results: <array>, key.unfiltered_result_count: <int64>}; got \(dict)"
      )
    }

    self.unfilteredResultCount = unfilteredResultCount
    self.memberAccessTypes = memberAccessTypes
    self.items =
      try results
      .map { try CompletionResult($0) }
      .sorted(by: { $0.semanticScore > $1.semanticScore })

    XCTAssertGreaterThanOrEqual(
      self.unfilteredResultCount,
      self.items.count,
      "unfiltered_result_count must be greater than or equal to the count of results"
    )
  }
}

private struct CompletionResult: Equatable, Sendable {
  nonisolated(unsafe) var kind: sourcekitd_api_uid_t
  var id: Int
  var name: String
  var description: String
  var sourcetext: String
  var module: String?
  var typename: String
  var textMatchScore: Double
  var semanticScore: Double
  var semanticScoreComponents: String?
  var priorityBucket: Int
  var isSystem: Bool
  var numBytesToErase: Int
  var hasDiagnostic: Bool
  var groupID: Int?

  init(_ dict: SKDResponseDictionary) throws {
    let keys = dict.sourcekitd.keys

    guard let kind: sourcekitd_api_uid_t = dict[keys.kind],
      let id: Int = dict[keys.identifier],
      let name: String = dict[keys.name],
      let description: String = dict[keys.description],
      let sourcetext: String = dict[keys.sourceText],
      let typename: String = dict[keys.typeName],
      let textMatchScore: Double = dict[keys.textMatchScore],
      let semanticScore: Double = dict[keys.semanticScore],
      let priorityBucket: Int = dict[keys.priorityBucket],
      let isSystem: Bool = dict[keys.isSystem],
      let hasDiagnostic: Bool = dict[keys.hasDiagnostic]
    else {
      throw TestError("Failed to decode CompletionResult. Received \(dict)")
    }

    self.kind = kind
    self.id = id
    self.name = name
    self.description = description
    self.sourcetext = sourcetext
    self.module = dict[keys.moduleName]
    self.typename = typename
    self.textMatchScore = textMatchScore
    self.semanticScore = semanticScore
    self.semanticScoreComponents = dict[keys.semanticScoreComponents]
    self.priorityBucket = priorityBucket
    self.isSystem = isSystem
    self.numBytesToErase = dict[keys.numBytesToErase] ?? 0
    self.hasDiagnostic = hasDiagnostic
    self.groupID = dict[keys.groupId]
    assert(self.groupID != 0)
  }
}

private struct CompletionDocumentation {
  var docComment: String? = nil
  var docFullAsXML: String? = nil
  var docBrief: String? = nil
  var associatedUSRs: [String] = []

  init(_ dict: SKDResponseDictionary) {
    let keys = dict.sourcekitd.keys
    self.docComment = dict[keys.docComment]
    self.docFullAsXML = dict[keys.docFullAsXML]
    self.docBrief = dict[keys.docBrief]
    self.associatedUSRs = dict[keys.associatedUSRs]?.asStringArray ?? []
  }
}

private struct CompletionDiagnostic {
  var severity: sourcekitd_api_uid_t
  var description: String

  init?(_ dict: SKDResponseDictionary) {
    let keys = dict.sourcekitd.keys
    guard
      let severity: sourcekitd_api_uid_t = dict[keys.severity],
      let description: String = dict[keys.description]
    else {
      return nil
    }
    self.severity = severity
    self.description = description
  }
}

private struct TestError: Error {
  let error: String

  init(_ message: String) {
    self.error = message
  }
}

// MARK: - sourcekitd convenience functions

struct CompletionRequestFlags: OptionSet {
  let rawValue: Int
  static let annotate: Self = .init(rawValue: 1 << 0)
  static let addInitsToTopLevel: Self = .init(rawValue: 1 << 1)
  static let addCallWithNoDefaultArgs: Self = .init(rawValue: 1 << 2)
  static let includeSemanticComponents: Self = .init(rawValue: 1 << 3)
}

fileprivate extension SourceKitD {
  @discardableResult
  nonisolated func openDocument(
    _ name: String,
    contents markedSource: String,
    compilerArguments: [String]? = nil
  ) async throws -> DocumentPositions {
    let (markers, textWithoutMarkers) = extractMarkers(markedSource)
    var compilerArguments = compilerArguments ?? [name]
    if let defaultSDKPath {
      compilerArguments += ["-sdk", defaultSDKPath]
    }
    let req = dictionary([
      keys.name: name,
      keys.sourceText: textWithoutMarkers,
      keys.syntacticOnly: 1,
      keys.compilerArgs: compilerArguments as [any SKDRequestValue],
    ])
    _ = try await send(\.editorOpen, req)
    return DocumentPositions(markers: markers, textWithoutMarkers: textWithoutMarkers)
  }

  nonisolated func editDocument(_ name: String, fromOffset offset: Int, length: Int, newContents: String) async throws {
    let req = dictionary([
      keys.name: name,
      keys.offset: offset,
      keys.length: length,
      keys.sourceText: newContents,
      keys.syntacticOnly: 1,
    ])

    _ = try await send(\.editorReplaceText, req)
  }

  nonisolated func closeDocument(_ name: String) async throws {
    let req = dictionary([
      keys.name: name
    ])

    _ = try await send(\.editorClose, req)
  }

  nonisolated func completeImpl(
    requestUID: any KeyPath<sourcekitd_api_requests, sourcekitd_api_uid_t> & Sendable,
    path: String,
    position: Position,
    filter: String,
    recentCompletions: [String]? = nil,
    flags: CompletionRequestFlags = [],
    useXPC: Bool = false,
    maxResults: Int? = nil,
    compilerArguments: [String]? = nil
  ) async throws -> CompletionResultSet {
    let options = dictionary([
      keys.useNewAPI: 1,
      keys.annotatedDescription: flags.contains(.annotate) ? 1 : 0,
      keys.addInitsToTopLevel: flags.contains(.addInitsToTopLevel) ? 1 : 0,
      keys.addCallWithNoDefaultArgs: flags.contains(.addCallWithNoDefaultArgs) ? 1 : 0,
      keys.includeSemanticComponents: flags.contains(.includeSemanticComponents) ? 1 : 0,
      keys.filterText: filter,
      keys.recentCompletions: recentCompletions as [any SKDRequestValue]?,
      keys.maxResults: maxResults,
    ])

    let req = dictionary([
      keys.line: position.line + 1,
      // Technically sourcekitd needs a UTF-8 index but we can assume there are no Unicode characters in the tests
      keys.column: position.utf16index + 1,
      keys.sourceFile: path,
      keys.codeCompleteOptions: options,
      keys.compilerArgs: compilerArguments as [any SKDRequestValue]?,
    ])

    let res = try await send(requestUID, req)
    return try CompletionResultSet(res)
  }

  nonisolated func completeOpen(
    path: String,
    position: Position,
    filter: String,
    recentCompletions: [String]? = nil,
    flags: CompletionRequestFlags = [],
    useXPC: Bool = false,
    maxResults: Int? = nil,
    compilerArguments: [String]? = nil
  ) async throws -> CompletionResultSet {
    return try await completeImpl(
      requestUID: \.codeCompleteOpen,
      path: path,
      position: position,
      filter: filter,
      recentCompletions: recentCompletions,
      flags: flags,
      useXPC: useXPC,
      maxResults: maxResults,
      compilerArguments: compilerArguments
    )
  }

  nonisolated func completeUpdate(
    path: String,
    position: Position,
    filter: String,
    flags: CompletionRequestFlags = [],
    useXPC: Bool = false,
    maxResults: Int? = nil
  ) async throws -> CompletionResultSet {
    return try await completeImpl(
      requestUID: \.codeCompleteUpdate,
      path: path,
      position: position,
      filter: filter,
      recentCompletions: nil,
      flags: flags,
      useXPC: useXPC,
      maxResults: maxResults,
      compilerArguments: nil
    )
  }

  nonisolated func completeClose(path: String, position: Position) async throws {
    let req = dictionary([
      keys.line: position.line + 1,
      // Technically sourcekitd needs a UTF-8 index but we can assume there are no Unicode characters in the tests
      keys.column: position.utf16index + 1,
      keys.sourceFile: path,
      keys.codeCompleteOptions: dictionary([keys.useNewAPI: 1]),
    ])

    _ = try await send(\.codeCompleteClose, req)
  }

  nonisolated func completeDocumentation(id: Int) async throws -> CompletionDocumentation {
    let resp = try await send(\.codeCompleteDocumentation, dictionary([keys.identifier: id]))
    return CompletionDocumentation(resp)
  }

  nonisolated func completeDiagnostic(id: Int) async throws -> CompletionDiagnostic? {
    let resp = try await send(\.codeCompleteDiagnostic, dictionary([keys.identifier: id]))

    return CompletionDiagnostic(resp)
  }

  nonisolated func dependencyUpdated() async throws {
    _ = try await send(\.dependencyUpdated, dictionary([:]))
  }

  nonisolated func setPopularAPI(popular: [String], unpopular: [String]) async throws {
    let req = dictionary([
      keys.codeCompleteOptions: dictionary([keys.useNewAPI: 1]),
      keys.popular: popular as [any SKDRequestValue],
      keys.unpopular: unpopular as [any SKDRequestValue],
    ])

    let resp = try await send(\.codeCompleteSetPopularAPI, req)
    XCTAssertEqual(resp[keys.useNewAPI], 1)
  }

  nonisolated func setPopularityIndex(
    scopedPopularityDataPath: String,
    popularModules: [String],
    notoriousModules: [String]
  ) async throws {
    let req = dictionary([
      keys.codeCompleteOptions: dictionary([keys.useNewAPI: 1]),
      keys.scopedPopularityTablePath: scopedPopularityDataPath,
      keys.popularModules: popularModules as [any SKDRequestValue],
      keys.notoriousModules: notoriousModules as [any SKDRequestValue],
    ])

    let resp = try await send(\.codeCompleteSetPopularAPI, req)
    XCTAssertEqual(resp[keys.useNewAPI], 1)
  }

  func setPopularityTable(_ popularityTable: PopularityTable) async throws {
    let symbolPopularity = popularityTable.symbolPopularity.map { key, value in
      dictionary([
        keys.popularityKey: key,
        keys.popularityValueIntBillion: Int(value.scoreComponent * 1_000_000_000),
      ])
    }
    let modulePopularity = popularityTable.modulePopularity.map { key, value in
      dictionary([
        keys.popularityKey: key,
        keys.popularityValueIntBillion: Int(value.scoreComponent * 1_000_000_000),
      ])
    }
    let req = dictionary([
      keys.codeCompleteOptions: dictionary([keys.useNewAPI: 1]),
      keys.symbolPopularity: symbolPopularity as [any SKDRequestValue],
      keys.modulePopularity: modulePopularity as [any SKDRequestValue],
    ])

    let resp = try await send(\.codeCompleteSetPopularAPI, req)
    XCTAssertEqual(resp[keys.useNewAPI], 1)
  }

  func perfTestSetup(path: String) async throws -> (Position, recent: [String]) {
    var content = """
      struct S {
        func test() {
          self.1️⃣
        }
      """

    #if DEBUG
    let numMethods = 1_000
    #else
    let numMethods = 100_000
    #endif

    var popular: [String] = []
    var unpopular: [String] = []

    for i in 0..<numMethods {
      content += "\n  func myMethodWithSomeWords\(i)() {}"
      if i % 200 == 0 {
        popular.append("myMethodWithSomeWords\(i)()")
      }
      if i % 200 == 3 {
        unpopular.append("myMethodWithSomeWords\(i)()")
      }
    }
    content += "\n}"

    var recent: [String] = []
    for _ in 0..<100 {
      recent.append("myMethodWithSomeWords\((0..<numMethods).randomElement()!)()")
    }

    let (markers, textWithoutMarker) = extractMarkers(content)
    let positions = DocumentPositions(markers: markers, textWithoutMarkers: textWithoutMarker)

    try await setPopularAPI(popular: popular, unpopular: unpopular)
    try await openDocument(path, contents: textWithoutMarker, compilerArguments: [path])
    return (positions["1️⃣"], recent)
  }
}

private struct ExpectationNotFulfilledError: Error {}

/// Run the given async block and block the current function until `body` terminates.
private func runAsync<T: Sendable>(_ body: @escaping @Sendable () async throws -> T) throws -> T {
  nonisolated(unsafe) var result: Result<T, any Error>!
  let expectation = XCTestExpectation(description: "")
  Task {
    do {
      result = .success(try await body())
    } catch {
      result = .failure(error)
    }
    expectation.fulfill()
  }
  let started = XCTWaiter.wait(for: [expectation], timeout: defaultTimeout)
  if started != .completed {
    throw ExpectationNotFulfilledError()
  }
  return try result.get()
}
