Skip to content

Commit 8c7c32d

Browse files
authored
Merge pull request #770 from ahoppen/ahoppen/inlayhint-edits
Don’t offer text edits from inlay hints if they are syntactically invalid
2 parents 66f1c0d + 6c99034 commit 8c7c32d

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,13 +1346,17 @@ extension SwiftLanguageServer {
13461346
.map { info -> InlayHint in
13471347
let position = info.range.upperBound
13481348
let label = ": \(info.printedType)"
1349+
let textEdits: [TextEdit]?
1350+
if info.canBeFollowedByTypeAnnotation {
1351+
textEdits = [TextEdit(range: position..<position, newText: label)]
1352+
} else {
1353+
textEdits = nil
1354+
}
13491355
return InlayHint(
13501356
position: position,
13511357
label: .string(label),
13521358
kind: .type,
1353-
textEdits: [
1354-
TextEdit(range: position..<position, newText: label)
1355-
]
1359+
textEdits: textEdits
13561360
)
13571361
}
13581362

Sources/SourceKitLSP/Swift/VariableTypeInfo.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,35 @@
1313
import Dispatch
1414
import LanguageServerProtocol
1515
import SourceKitD
16+
import SwiftSyntax
17+
18+
fileprivate extension TokenSyntax {
19+
/// Returns `false` if it is known that this token can’t be followed by a type
20+
/// annotation.
21+
var canBeFollowedByTypeAnnotation: Bool {
22+
var node = Syntax(self)
23+
LOOP: while let parent = node.parent {
24+
switch parent.kind {
25+
case .caseItem, .closureParam:
26+
// case items (inside a switch) and closure parameters can’t have type
27+
// annotations.
28+
return false
29+
case .codeBlockItem, .memberDeclListItem:
30+
// Performance optimization. If we walked the parents up to code block item,
31+
// we can’t enter a case item or closure param anymore. No need walking
32+
// the tree any further.
33+
break LOOP
34+
default:
35+
break
36+
}
37+
node = parent
38+
}
39+
40+
// By default, assume that the token can be followed by a type annotation as
41+
// most locations that produce a variable type info can.
42+
return true
43+
}
44+
}
1645

1746
/// A typed variable as returned by sourcekitd's CollectVariableType.
1847
struct VariableTypeInfo {
@@ -22,6 +51,9 @@ struct VariableTypeInfo {
2251
var printedType: String
2352
/// Whether the variable has an explicit type annotation in the source file.
2453
var hasExplicitType: Bool
54+
/// Whether we should suggest making an edit to add the type annotation to the
55+
/// source file.
56+
var canBeFollowedByTypeAnnotation: Bool
2557

2658
init?(_ dict: SKDResponseDictionary, in snapshot: DocumentSnapshot) {
2759
let keys = dict.sourcekitd.keys
@@ -34,10 +66,12 @@ struct VariableTypeInfo {
3466
let hasExplicitType: Bool = dict[keys.variable_type_explicit] else {
3567
return nil
3668
}
69+
let tokenAtOffset = snapshot.tokens.syntaxTree?.token(at: AbsolutePosition(utf8Offset: offset))
3770

3871
self.range = startIndex..<endIndex
3972
self.printedType = printedType
4073
self.hasExplicitType = hasExplicitType
74+
self.canBeFollowedByTypeAnnotation = tokenAtOffset?.canBeFollowedByTypeAnnotation ?? true
4175
}
4276
}
4377

Tests/SourceKitLSPTests/InlayHintTests.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,18 @@ final class InlayHintTests: XCTestCase {
6161
}
6262
}
6363

64-
private func makeInlayHint(position: Position, kind: InlayHintKind, label: String) -> InlayHint {
65-
InlayHint(
64+
private func makeInlayHint(position: Position, kind: InlayHintKind, label: String, hasEdit: Bool = true) -> InlayHint {
65+
let textEdits: [TextEdit]?
66+
if hasEdit {
67+
textEdits = [TextEdit(range: position..<position, newText: label)]
68+
} else {
69+
textEdits = nil
70+
}
71+
return InlayHint(
6672
position: position,
6773
label: .string(label),
6874
kind: kind,
69-
textEdits: [
70-
TextEdit(range: position..<position, newText: label)
71-
]
75+
textEdits: textEdits
7276
)
7377
}
7478

@@ -205,7 +209,8 @@ final class InlayHintTests: XCTestCase {
205209
makeInlayHint(
206210
position: Position(line: 3, utf16index: 31),
207211
kind: .type,
208-
label: ": String"
212+
label: ": String",
213+
hasEdit: false
209214
),
210215
makeInlayHint(
211216
position: Position(line: 4, utf16index: 40),

0 commit comments

Comments
 (0)