diff --git a/Sources/SourceKitLSP/LanguageService.swift b/Sources/SourceKitLSP/LanguageService.swift index 2294fdec6..28bd61096 100644 --- a/Sources/SourceKitLSP/LanguageService.swift +++ b/Sources/SourceKitLSP/LanguageService.swift @@ -216,6 +216,11 @@ package protocol LanguageService: AnyObject, Sendable { manager: OnDiskDocumentManager ) async throws -> String + /// Check if the given position is on a literal value (string, integer, boolean, etc.). + /// + /// Literal values should not support jump-to-definition since they don't have a "definition" location. + func isPositionOnLiteral(_ position: Position, in uri: DocumentURI) async -> Bool + /// Request a generated interface of a module to display in the IDE. /// /// - Parameters: @@ -395,6 +400,10 @@ package extension LanguageService { throw ResponseError.requestNotImplemented(SymbolInfoRequest.self) } + func isPositionOnLiteral(_ position: Position, in uri: DocumentURI) async -> Bool { + return false + } + func symbolGraph( for snapshot: DocumentSnapshot, at position: Position diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 4237895df..b127d7fc4 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -2044,6 +2044,11 @@ extension SourceKitLSPServer { workspace: Workspace, languageService: any LanguageService ) async throws -> [Location] { + // Check if the position is on a literal value (string, integer, etc.) + if await languageService.isPositionOnLiteral(req.position, in: req.textDocument.uri) { + return [] + } + let symbols = try await languageService.symbolInfo( SymbolInfoRequest( textDocument: req.textDocument, diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index 639a5a64a..1eb150099 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -1293,3 +1293,63 @@ extension sourcekitd_api_uid_t { } } } + +// MARK: - Literal Detection + +extension SwiftLanguageService { + /// Check if the given position in the document is on a literal expression. + /// + /// Literal expressions (string, integer, boolean, float, array, dictionary) should not support + /// jump-to-definition since they don't have a "definition" location - they are values typed + /// directly in the code. + /// + package func isPositionOnLiteral( + _ position: Position, + in uri: DocumentURI + ) async -> Bool { + guard let snapshot = try? await latestSnapshot(for: uri) else { + return false + } + + let tree = await syntaxTreeManager.syntaxTree(for: snapshot) + let absolutePosition = snapshot.absolutePosition(of: position) + + // Helper function to check if a token is part of a literal expression + func isTokenInLiteral(_ token: TokenSyntax) -> Bool { + var currentNode: Syntax? = Syntax(token) + while let node = currentNode { + // Check all literal expression types + if node.is(StringLiteralExprSyntax.self) + || node.is(IntegerLiteralExprSyntax.self) + || node.is(FloatLiteralExprSyntax.self) + || node.is(BooleanLiteralExprSyntax.self) + || node.is(ArrayExprSyntax.self) + || node.is(DictionaryExprSyntax.self) + || node.is(NilLiteralExprSyntax.self) + { + return true + } + currentNode = node.parent + } + return false + } + + // Find the token at this position + if let token = tree.token(at: absolutePosition) { + if isTokenInLiteral(token) { + return true + } + } + + // Also check the token before the cursor position, as users might click right after a literal + if absolutePosition.utf8Offset > 0, + let tokenBefore = tree.token(at: AbsolutePosition(utf8Offset: absolutePosition.utf8Offset - 1)) + { + if isTokenInLiteral(tokenBefore) { + return true + } + } + + return false + } +} diff --git a/Tests/SourceKitLSPTests/DefinitionTests.swift b/Tests/SourceKitLSPTests/DefinitionTests.swift index e8940aafc..39f59d704 100644 --- a/Tests/SourceKitLSPTests/DefinitionTests.swift +++ b/Tests/SourceKitLSPTests/DefinitionTests.swift @@ -689,4 +689,43 @@ class DefinitionTests: SourceKitLSPTestCase { ) XCTAssertEqual(response?.locations?.map(\.uri), [try project.uri(for: "Test.h")]) } + + func testDefinitionOnLiteralsShouldReturnEmpty() async throws { + let testClient = try await TestSourceKitLSPClient() + let uri = DocumentURI(for: .swift) + + let positions = testClient.openDocument( + """ + let message = "Hello"1️⃣ + let count = 422️⃣ + let flag = true3️⃣ + let items = [1, 2, 3]4️⃣ + """, + uri: uri + ) + + // Test string literal + let stringResponse = try await testClient.send( + DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) + ) + XCTAssertNil(stringResponse, "Jump-to-definition should not work on string literals") + + // Test integer literal + let intResponse = try await testClient.send( + DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"]) + ) + XCTAssertNil(intResponse, "Jump-to-definition should not work on integer literals") + + // Test boolean literal + let boolResponse = try await testClient.send( + DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3️⃣"]) + ) + XCTAssertNil(boolResponse, "Jump-to-definition should not work on boolean literals") + + // Test array literal + let arrayResponse = try await testClient.send( + DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["4️⃣"]) + ) + XCTAssertNil(arrayResponse, "Jump-to-definition should not work on array literals") + } }