mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
This adds a sourcekitd plugin that drives the code completion requests. It also includes a `CompletionScoring` module that’s used to rank code completion results based on their contextual match, allowing us to show more relevant code completion results at the top.
363 lines
11 KiB
Swift
363 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 Foundation
|
|
|
|
package struct SemanticClassification: Equatable {
|
|
package var completionKind: CompletionKind
|
|
package var popularity: Popularity
|
|
package var moduleProximity: ModuleProximity
|
|
package var scopeProximity: ScopeProximity
|
|
package var structuralProximity: StructuralProximity
|
|
package var typeCompatibility: TypeCompatibility
|
|
package var synchronicityCompatibility: SynchronicityCompatibility
|
|
package var availability: Availability
|
|
package var flair: Flair
|
|
|
|
/// - Note: There is no natural order to these arguments, so they're alphabetical.
|
|
package init(
|
|
availability: Availability,
|
|
completionKind: CompletionKind,
|
|
flair: Flair,
|
|
moduleProximity: ModuleProximity,
|
|
popularity: Popularity,
|
|
scopeProximity: ScopeProximity,
|
|
structuralProximity: StructuralProximity,
|
|
synchronicityCompatibility: SynchronicityCompatibility,
|
|
typeCompatibility: TypeCompatibility
|
|
) {
|
|
self.availability = availability
|
|
self.completionKind = completionKind
|
|
self.flair = flair
|
|
self.moduleProximity = moduleProximity
|
|
self.popularity = popularity
|
|
self.scopeProximity = scopeProximity
|
|
self.structuralProximity = structuralProximity
|
|
self.synchronicityCompatibility = synchronicityCompatibility
|
|
self.typeCompatibility = typeCompatibility
|
|
}
|
|
|
|
package var score: Double {
|
|
let score =
|
|
availability.scoreComponent
|
|
* completionKind.scoreComponent
|
|
* flair.scoreComponent
|
|
* moduleProximity.scoreComponent
|
|
* popularity.scoreComponent
|
|
* scopeProximity.scoreComponent
|
|
* structuralProximity.scoreComponent
|
|
* synchronicityCompatibility.scoreComponent
|
|
* typeCompatibility.scoreComponent
|
|
* globalVariablesPenalty
|
|
|
|
return score
|
|
}
|
|
|
|
private var globalVariablesPenalty: Double {
|
|
// Global types and functions are fine, global variables and c enum cases in the global space are not.
|
|
if (scopeProximity == .global) && ((completionKind == .variable) || (completionKind == .enumCase)) {
|
|
return 0.75
|
|
}
|
|
return 1.0
|
|
}
|
|
|
|
package struct ComponentDebugDescription {
|
|
package let name: String
|
|
package let instance: String
|
|
package let scoreComponent: Double
|
|
}
|
|
|
|
private var scoreComponents: [CompletionScoreComponent] {
|
|
return [
|
|
availability,
|
|
completionKind,
|
|
flair,
|
|
RawCompletionScoreComponent(
|
|
name: "symbolPopularity",
|
|
instance: "\(popularity.symbolComponent)",
|
|
scoreComponent: popularity.symbolComponent
|
|
),
|
|
RawCompletionScoreComponent(
|
|
name: "modulePopularity",
|
|
instance: "\(popularity.moduleComponent)",
|
|
scoreComponent: popularity.moduleComponent
|
|
),
|
|
moduleProximity,
|
|
scopeProximity,
|
|
structuralProximity,
|
|
synchronicityCompatibility,
|
|
typeCompatibility,
|
|
RawCompletionScoreComponent(
|
|
name: "globalVariablesPenalty",
|
|
instance: "\(globalVariablesPenalty != 1.0)",
|
|
scoreComponent: globalVariablesPenalty
|
|
),
|
|
]
|
|
}
|
|
|
|
package var componentsDebugDescription: [ComponentDebugDescription] {
|
|
return scoreComponents.map { $0.componentDebugDescription }
|
|
}
|
|
}
|
|
|
|
extension SemanticClassification: BinaryCodable {
|
|
package init(_ decoder: inout BinaryDecoder) throws {
|
|
availability = try Availability(&decoder)
|
|
completionKind = try CompletionKind(&decoder)
|
|
flair = try Flair(&decoder)
|
|
moduleProximity = try ModuleProximity(&decoder)
|
|
popularity = try Popularity(&decoder)
|
|
scopeProximity = try ScopeProximity(&decoder)
|
|
structuralProximity = try StructuralProximity(&decoder)
|
|
synchronicityCompatibility = try SynchronicityCompatibility(&decoder)
|
|
typeCompatibility = try TypeCompatibility(&decoder)
|
|
}
|
|
|
|
package func encode(_ encoder: inout BinaryEncoder) {
|
|
encoder.write(availability)
|
|
encoder.write(completionKind)
|
|
encoder.write(flair)
|
|
encoder.write(moduleProximity)
|
|
encoder.write(popularity)
|
|
encoder.write(scopeProximity)
|
|
encoder.write(structuralProximity)
|
|
encoder.write(synchronicityCompatibility)
|
|
encoder.write(typeCompatibility)
|
|
}
|
|
}
|
|
|
|
/// Published serialization methods
|
|
extension SemanticClassification {
|
|
package func byteRepresentation() -> [UInt8] {
|
|
binaryCodedRepresentation(contentVersion: 0)
|
|
}
|
|
|
|
package init(byteRepresentation: [UInt8]) throws {
|
|
try self.init(binaryCodedRepresentation: byteRepresentation)
|
|
}
|
|
|
|
package static func byteRepresentation(classifications: [Self]) -> [UInt8] {
|
|
classifications.binaryCodedRepresentation(contentVersion: 0)
|
|
}
|
|
|
|
package static func classifications(byteRepresentations: [UInt8]) throws -> [Self] {
|
|
try [Self].init(binaryCodedRepresentation: byteRepresentations)
|
|
}
|
|
}
|
|
|
|
/// Used for debugging.
|
|
fileprivate protocol CompletionScoreComponent {
|
|
var name: String { get }
|
|
|
|
var instance: String { get }
|
|
|
|
/// Return a value in 0...2.
|
|
///
|
|
/// Think of values between 0 and 1 as penalties, 1 as neutral, and values from 1 and 2 as bonuses.
|
|
var scoreComponent: Double { get }
|
|
}
|
|
|
|
extension CompletionScoreComponent {
|
|
var componentDebugDescription: SemanticClassification.ComponentDebugDescription {
|
|
return .init(name: name, instance: instance, scoreComponent: scoreComponent)
|
|
}
|
|
}
|
|
|
|
/// Used for components that don't have a dedicated model.
|
|
private struct RawCompletionScoreComponent: CompletionScoreComponent {
|
|
let name: String
|
|
let instance: String
|
|
let scoreComponent: Double
|
|
}
|
|
|
|
internal let unknownScore = 0.750
|
|
internal let inapplicableScore = 1.000
|
|
internal let unspecifiedScore = 1.000
|
|
|
|
private let localVariableScore = CompletionKind.variable.scoreComponent * ScopeProximity.local.scoreComponent
|
|
private let globalTypeScore = CompletionKind.type.scoreComponent * ScopeProximity.global.scoreComponent
|
|
internal let localVariableToGlobalTypeScoreRatio = localVariableScore / globalTypeScore
|
|
|
|
extension CompletionKind: CompletionScoreComponent {
|
|
fileprivate var name: String { "CompletionKind" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
fileprivate var scoreComponent: Double {
|
|
switch self {
|
|
case .keyword: return 1.000
|
|
case .enumCase: return 1.100
|
|
case .variable: return 1.075
|
|
case .initializer: return 1.020
|
|
case .argumentLabels: return 2.000
|
|
case .function: return 1.025
|
|
case .type: return 1.025
|
|
case .template: return 1.100
|
|
case .module: return 0.925
|
|
case .other: return 1.000
|
|
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Flair: CompletionScoreComponent {
|
|
fileprivate var name: String { "Flair" }
|
|
fileprivate var instance: String { "\(self.debugDescription)" }
|
|
internal var scoreComponent: Double {
|
|
var total = 1.0
|
|
if self.contains(.oldExpressionSpecific_pleaseAddSpecificCaseToThisEnum) {
|
|
total *= 1.5
|
|
}
|
|
if self.contains(.chainedCallToSuper) {
|
|
total *= 1.5
|
|
}
|
|
if self.contains(.chainedMember) {
|
|
total *= 0.3
|
|
}
|
|
if self.contains(.swiftUIModifierOnSelfWhileBuildingSelf) {
|
|
total *= 0.3
|
|
}
|
|
if self.contains(.swiftUIUnlikelyViewMember) {
|
|
total *= 0.125
|
|
}
|
|
if self.contains(.commonKeywordAtCurrentPosition) {
|
|
total *= 1.25
|
|
}
|
|
if self.contains(.rareKeywordAtCurrentPosition) {
|
|
total *= 0.75
|
|
}
|
|
if self.contains(.rareTypeAtCurrentPosition) {
|
|
total *= 0.75
|
|
}
|
|
if self.contains(.expressionAtNonScriptOrMainFileScope) {
|
|
total *= 0.125
|
|
}
|
|
if self.contains(.rareMemberWithCommonName) {
|
|
total *= 0.75
|
|
}
|
|
if self.contains(._situationallyLikely) {
|
|
total *= 1.25
|
|
}
|
|
if self.contains(._situationallyUnlikely) {
|
|
total *= 0.75
|
|
}
|
|
if self.contains(._situationallyInvalid) {
|
|
total *= 0.125
|
|
}
|
|
return total
|
|
}
|
|
}
|
|
|
|
extension ModuleProximity: CompletionScoreComponent {
|
|
fileprivate var name: String { "ModuleProximity" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
fileprivate var scoreComponent: Double {
|
|
switch self {
|
|
case .imported(0): return 1.0500
|
|
case .imported(1): return 1.0250
|
|
case .imported(_): return 1.0125
|
|
case .importable: return 0.5000
|
|
case .invalid: return 0.2500
|
|
|
|
case .inapplicable: return inapplicableScore
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ScopeProximity: CompletionScoreComponent {
|
|
fileprivate var name: String { "ScopeProximity" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
fileprivate var scoreComponent: Double {
|
|
switch self {
|
|
case .local: return 1.500
|
|
case .argument: return 1.450
|
|
case .container: return 1.350
|
|
case .inheritedContainer: return 1.325
|
|
case .outerContainer: return 1.325
|
|
case .global: return 0.950
|
|
|
|
case .inapplicable: return inapplicableScore
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|
|
|
|
extension StructuralProximity: CompletionScoreComponent {
|
|
fileprivate var name: String { "StructuralProximity" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
fileprivate var scoreComponent: Double {
|
|
switch self {
|
|
case .project(fileSystemHops: 0): return 1.010
|
|
case .project(fileSystemHops: 1): return 1.005
|
|
case .project(fileSystemHops: _): return 1.000
|
|
case .sdk: return 0.995
|
|
|
|
case .inapplicable: return inapplicableScore
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SynchronicityCompatibility: CompletionScoreComponent {
|
|
fileprivate var name: String { "SynchronicityCompatibility" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
fileprivate var scoreComponent: Double {
|
|
switch self {
|
|
case .compatible: return 1.00
|
|
case .convertible: return 0.90
|
|
case .incompatible: return 0.50
|
|
|
|
case .inapplicable: return inapplicableScore
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|
|
|
|
extension TypeCompatibility: CompletionScoreComponent {
|
|
fileprivate var name: String { "TypeCompatibility" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
fileprivate var scoreComponent: Double {
|
|
switch self {
|
|
case .compatible: return 1.300
|
|
case .unrelated: return 0.900
|
|
case .invalid: return 0.300
|
|
|
|
case .inapplicable: return inapplicableScore
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Availability: CompletionScoreComponent {
|
|
fileprivate var name: String { "Availability" }
|
|
fileprivate var instance: String { "\(self)" }
|
|
internal var scoreComponent: Double {
|
|
switch self {
|
|
case .available: return 1.00
|
|
case .unavailable: return 0.40
|
|
case .softDeprecated,
|
|
.deprecated:
|
|
return 0.50
|
|
|
|
case .inapplicable: return inapplicableScore
|
|
case .unspecified: return unspecifiedScore
|
|
case .unknown: return unknownScore
|
|
}
|
|
}
|
|
}
|