Skip to content

Commit af51dc3

Browse files
authored
Merge pull request #1157 from ahoppen/ahoppen/6.0/update-from-main-04-02
[6.0] Merge changes from `main`
2 parents 53ea6d9 + ca09055 commit af51dc3

13 files changed

+544
-160
lines changed

Sources/LanguageServerProtocol/Message.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import Dispatch
14-
1513
public protocol MessageType: Codable, Sendable {}
1614

1715
/// `RequestType` with no associated type or same-type requirements. Most users should prefer

Sources/SKTestSupport/Array+SyntaxHighlightingToken.swift

Lines changed: 0 additions & 53 deletions
This file was deleted.

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,12 @@ public enum SkipUnless {
106106
let response = try unwrap(
107107
await testClient.send(DocumentSemanticTokensRequest(textDocument: TextDocumentIdentifier(uri)))
108108
)
109-
let tokens = [SyntaxHighlightingToken](lspEncodedTokens: response.data)
109+
110+
let tokens = SyntaxHighlightingTokens(lspEncodedTokens: response.data)
110111

111112
// If we don't have semantic token support in sourcekitd, the second token is an identifier based on the syntax
112113
// tree, not a property.
113-
return tokens != [
114+
return tokens.tokens != [
114115
SyntaxHighlightingToken(
115116
range: Position(line: 0, utf16index: 0)..<Position(line: 0, utf16index: 1),
116117
kind: .number,

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
add_library(SourceKitLSP STATIC
33
CapabilityRegistry.swift
44
DocumentManager.swift
5+
IndexOutOfDateChecker.swift
56
IndexStoreDB+MainFilesProvider.swift
67
LanguageService.swift
78
Rename.swift
@@ -39,6 +40,7 @@ target_sources(SourceKitLSP PRIVATE
3940
Swift/SwiftLanguageService.swift
4041
Swift/SymbolInfo.swift
4142
Swift/SyntaxHighlightingToken.swift
43+
Swift/SyntaxHighlightingTokens.swift
4244
Swift/SyntaxHighlightingTokenParser.swift
4345
Swift/SyntaxTreeManager.swift
4446
Swift/VariableTypeInfo.swift
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import IndexStoreDB
15+
import LSPLogging
16+
17+
/// Helper class to check if symbols from the index are up-to-date or if the source file has been modified after it was
18+
/// indexed.
19+
///
20+
/// The checker caches mod dates of source files. It should thus not be long lived. Its intended lifespan is the
21+
/// evaluation of a single request.
22+
struct IndexOutOfDateChecker {
23+
/// The last modification time of a file. Can also represent the fact that the file does not exist.
24+
private enum ModificationTime {
25+
case fileDoesNotExist
26+
case date(Date)
27+
}
28+
29+
private enum Error: Swift.Error, CustomStringConvertible {
30+
case fileAttributesDontHaveModificationDate
31+
32+
var description: String {
33+
switch self {
34+
case .fileAttributesDontHaveModificationDate:
35+
return "File attributes don't contain a modification date"
36+
}
37+
}
38+
}
39+
40+
/// File paths to modification times that have already been computed.
41+
private var modTimeCache: [String: ModificationTime] = [:]
42+
43+
private func modificationDateUncached(of path: String) throws -> ModificationTime {
44+
do {
45+
let attributes = try FileManager.default.attributesOfItem(atPath: path)
46+
guard let modificationDate = attributes[FileAttributeKey.modificationDate] as? Date else {
47+
throw Error.fileAttributesDontHaveModificationDate
48+
}
49+
return .date(modificationDate)
50+
} catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError {
51+
return .fileDoesNotExist
52+
}
53+
}
54+
55+
private mutating func modificationDate(of path: String) throws -> ModificationTime {
56+
if let cached = modTimeCache[path] {
57+
return cached
58+
}
59+
let modTime = try modificationDateUncached(of: path)
60+
modTimeCache[path] = modTime
61+
return modTime
62+
}
63+
64+
/// Returns `true` if the source file for the given symbol location exists and has not been modified after it has been
65+
/// indexed.
66+
mutating func isUpToDate(_ symbolLocation: SymbolLocation) -> Bool {
67+
do {
68+
let sourceFileModificationDate = try modificationDate(of: symbolLocation.path)
69+
switch sourceFileModificationDate {
70+
case .fileDoesNotExist:
71+
return false
72+
case .date(let sourceFileModificationDate):
73+
return sourceFileModificationDate <= symbolLocation.timestamp
74+
}
75+
} catch {
76+
logger.fault("Unable to determine if SymbolLocation is up-to-date: \(error.forLogging)")
77+
return true
78+
}
79+
}
80+
81+
/// Return `true` if a unit file has been indexed for the given file path after its last modification date.
82+
///
83+
/// This means that at least a single build configuration of this file has been indexed since its last modification.
84+
mutating func indexHasUpToDateUnit(for filePath: String, index: IndexStoreDB) -> Bool {
85+
guard let lastUnitDate = index.dateOfLatestUnitFor(filePath: filePath) else {
86+
return false
87+
}
88+
do {
89+
let sourceModificationDate = try modificationDate(of: filePath)
90+
switch sourceModificationDate {
91+
case .fileDoesNotExist:
92+
return false
93+
case .date(let sourceModificationDate):
94+
return sourceModificationDate <= lastUnitDate
95+
}
96+
} catch {
97+
logger.fault("Unable to determine if source file has up-to-date unit: \(error.forLogging)")
98+
return true
99+
}
100+
}
101+
}

Sources/SourceKitLSP/LanguageService.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ public protocol LanguageService: AnyObject {
195195

196196
func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny?
197197

198+
/// Perform a syntactic scan of the file at the given URI for test cases and test classes.
199+
///
200+
/// This is used as a fallback to show the test cases in a file if the index for a given file is not up-to-date.
201+
func syntacticDocumentTests(for uri: DocumentURI) async throws -> [WorkspaceSymbolItem]?
202+
198203
/// Crash the language server. Should be used for crash recovery testing only.
199204
func _crash() async
200205
}

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import SwiftSyntax
1919

2020
extension SwiftLanguageService {
2121
/// Requests the semantic highlighting tokens for the given snapshot from sourcekitd.
22-
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> [SyntaxHighlightingToken]? {
22+
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> SyntaxHighlightingTokens? {
2323
guard let buildSettings = await self.buildSettings(for: snapshot.uri), !buildSettings.isFallback else {
2424
return nil
2525
}
@@ -35,6 +35,7 @@ extension SwiftLanguageService {
3535
guard let skTokens: SKDResponseArray = dict[keys.semanticTokens] else {
3636
return nil
3737
}
38+
3839
return SyntaxHighlightingTokenParser(sourcekitd: sourcekitd).parseTokens(skTokens, in: snapshot)
3940
}
4041

@@ -49,7 +50,7 @@ extension SwiftLanguageService {
4950
private func mergedAndSortedTokens(
5051
for snapshot: DocumentSnapshot,
5152
in range: Range<Position>? = nil
52-
) async throws -> [SyntaxHighlightingToken] {
53+
) async throws -> SyntaxHighlightingTokens {
5354
async let tree = syntaxTreeManager.syntaxTree(for: snapshot)
5455
let semanticTokens = await orLog("Loading semantic tokens") { try await semanticHighlightingTokens(for: snapshot) }
5556

@@ -59,11 +60,16 @@ extension SwiftLanguageService {
5960
} else {
6061
ByteSourceRange(offset: 0, length: await tree.totalLength.utf8Length)
6162
}
62-
return
63+
64+
let tokens =
6365
await tree
6466
.classifications(in: range)
65-
.flatMap({ $0.highlightingTokens(in: snapshot) })
66-
.mergingTokens(with: semanticTokens ?? [])
67+
.map { $0.highlightingTokens(in: snapshot) }
68+
.reduce(into: SyntaxHighlightingTokens(tokens: [])) { $0.tokens += $1.tokens }
69+
70+
return
71+
tokens
72+
.mergingTokens(with: semanticTokens ?? SyntaxHighlightingTokens(tokens: []))
6773
.sorted { $0.start < $1.start }
6874
}
6975

@@ -102,28 +108,30 @@ extension Range where Bound == Position {
102108
}
103109

104110
extension SyntaxClassifiedRange {
105-
fileprivate func highlightingTokens(in snapshot: DocumentSnapshot) -> [SyntaxHighlightingToken] {
111+
fileprivate func highlightingTokens(in snapshot: DocumentSnapshot) -> SyntaxHighlightingTokens {
106112
guard let (kind, modifiers) = self.kind.highlightingKindAndModifiers else {
107-
return []
113+
return SyntaxHighlightingTokens(tokens: [])
108114
}
109115

110116
guard
111117
let start: Position = snapshot.positionOf(utf8Offset: self.offset),
112118
let end: Position = snapshot.positionOf(utf8Offset: self.endOffset)
113119
else {
114-
return []
120+
return SyntaxHighlightingTokens(tokens: [])
115121
}
116122

117123
let multiLineRange = start..<end
118124
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
119125

120-
return ranges.map {
126+
let tokens = ranges.map {
121127
SyntaxHighlightingToken(
122128
range: $0,
123129
kind: kind,
124130
modifiers: modifiers
125131
)
126132
}
133+
134+
return SyntaxHighlightingTokens(tokens: tokens)
127135
}
128136
}
129137

Sources/SourceKitLSP/Swift/SyntaxHighlightingToken.swift

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,48 +48,6 @@ public struct SyntaxHighlightingToken: Hashable {
4848
}
4949
}
5050

51-
extension Array where Element == SyntaxHighlightingToken {
52-
/// The LSP representation of syntax highlighting tokens. Note that this
53-
/// requires the tokens in this array to be sorted.
54-
public var lspEncoded: [UInt32] {
55-
var previous = Position(line: 0, utf16index: 0)
56-
var rawTokens: [UInt32] = []
57-
rawTokens.reserveCapacity(count * 5)
58-
59-
for token in self {
60-
let lineDelta = token.start.line - previous.line
61-
let charDelta =
62-
token.start.utf16index - (
63-
// The character delta is relative to the previous token's start
64-
// only if the token is on the previous token's line.
65-
previous.line == token.start.line ? previous.utf16index : 0)
66-
67-
// We assert that the tokens are actually sorted
68-
assert(lineDelta >= 0)
69-
assert(charDelta >= 0)
70-
71-
previous = token.start
72-
rawTokens += [
73-
UInt32(lineDelta),
74-
UInt32(charDelta),
75-
UInt32(token.utf16length),
76-
token.kind.tokenType,
77-
token.modifiers.rawValue,
78-
]
79-
}
80-
81-
return rawTokens
82-
}
83-
84-
/// Merges the tokens in this array into a new token array,
85-
/// preferring the given array's tokens if duplicate ranges are
86-
/// found.
87-
public func mergingTokens(with other: [SyntaxHighlightingToken]) -> [SyntaxHighlightingToken] {
88-
let otherRanges = Set(other.map(\.range))
89-
return filter { !otherRanges.contains($0.range) } + other
90-
}
91-
}
92-
9351
extension SemanticTokenTypes {
9452
/// **(LSP Extension)**
9553
public static let identifier = Self("identifier")

Sources/SourceKitLSP/Swift/SyntaxHighlightingTokenParser.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ struct SyntaxHighlightingTokenParser {
2525
private func parseTokens(
2626
_ response: SKDResponseDictionary,
2727
in snapshot: DocumentSnapshot,
28-
into tokens: inout [SyntaxHighlightingToken]
28+
into tokens: inout SyntaxHighlightingTokens
2929
) {
3030
let keys = sourcekitd.keys
3131

@@ -52,7 +52,7 @@ struct SyntaxHighlightingTokenParser {
5252
let multiLineRange = start..<end
5353
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
5454

55-
tokens += ranges.map {
55+
tokens.tokens += ranges.map {
5656
SyntaxHighlightingToken(
5757
range: $0,
5858
kind: kind,
@@ -70,16 +70,16 @@ struct SyntaxHighlightingTokenParser {
7070
private func parseTokens(
7171
_ response: SKDResponseArray,
7272
in snapshot: DocumentSnapshot,
73-
into tokens: inout [SyntaxHighlightingToken]
73+
into tokens: inout SyntaxHighlightingTokens
7474
) {
7575
response.forEach { (_, value) in
7676
parseTokens(value, in: snapshot, into: &tokens)
7777
return true
7878
}
7979
}
8080

81-
func parseTokens(_ response: SKDResponseArray, in snapshot: DocumentSnapshot) -> [SyntaxHighlightingToken] {
82-
var tokens: [SyntaxHighlightingToken] = []
81+
func parseTokens(_ response: SKDResponseArray, in snapshot: DocumentSnapshot) -> SyntaxHighlightingTokens {
82+
var tokens: SyntaxHighlightingTokens = SyntaxHighlightingTokens(tokens: [])
8383
parseTokens(response, in: snapshot, into: &tokens)
8484
return tokens
8585
}

0 commit comments

Comments
 (0)