Skip to content

Commit e2f45ef

Browse files
committed
Correctly handle raw identifiers for swift-testing
Make sure we insert backticks if needed for the test identifier, and drop the parameter names from the display name if a raw identifier is used.
1 parent dfd9189 commit e2f45ef

File tree

2 files changed

+161
-6
lines changed

2 files changed

+161
-6
lines changed

Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,20 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor {
305305
return visitTypeOrExtensionDecl(node, typeNames: [identifier.name])
306306
}
307307

308+
/// If the given identifier requires backticks to be a valid decl identifier,
309+
/// applies backticks and returns `true` along with the new name. Otherwise
310+
/// returns `false` with the original name.
311+
func backtickIfNeeded(_ ident: Identifier) -> (backticked: Bool, name: String) {
312+
let name = ident.name
313+
if name == "``" {
314+
// Special case: `` stays as ``. Any other backticked name will have
315+
// been stripped by Identifier.
316+
return (true, name)
317+
}
318+
let needsBackticks = !name.isValidSwiftIdentifier(for: .variableName)
319+
return (needsBackticks, needsBackticks ? "`\(name)`" : name)
320+
}
321+
308322
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
309323
let testAttribute = node.attributes
310324
.compactMap { $0.as(AttributeSyntax.self) }
@@ -318,18 +332,28 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor {
318332
return .skipChildren
319333
}
320334

321-
let parameters = node.signature.parameterClause.parameters.map {
322-
"\($0.firstName.identifier?.name ?? $0.firstName.text):"
335+
let parameters = node.signature.parameterClause.parameters.map { param in
336+
let result =
337+
if let identifier = param.firstName.identifier {
338+
backtickIfNeeded(identifier).name
339+
} else {
340+
// Something like `_`, leave as-is.
341+
param.firstName.text
342+
}
343+
return "\(result):"
323344
}.joined()
324-
let name = "\(identifier.name)(\(parameters))"
345+
346+
let (hasBackticks, baseName) = backtickIfNeeded(identifier)
347+
let fullName = "\(baseName)(\(parameters))"
348+
let displayName = attributeData.displayName ?? (hasBackticks ? identifier.name : fullName)
325349

326350
let range = snapshot.absolutePositionRange(
327351
of: node.positionAfterSkippingLeadingTrivia..<node.endPositionBeforeTrailingTrivia
328352
)
329353
let testItem = AnnotatedTestItem(
330354
testItem: TestItem(
331-
id: (parentTypeNames + [name]).joined(separator: "/"),
332-
label: attributeData.displayName ?? name,
355+
id: (parentTypeNames + [fullName]).joined(separator: "/"),
356+
label: displayName,
333357
disabled: attributeData.isDisabled || allTestsDisabled,
334358
style: TestStyle.swiftTesting,
335359
location: Location(uri: snapshot.uri, range: range),

Tests/SourceKitLSPTests/DocumentTestDiscoveryTests.swift

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,11 @@ final class DocumentTestDiscoveryTests: XCTestCase {
621621
func oneIsTwo() {
622622
#expect(1 == 2)
623623
}2️⃣
624+
625+
3️⃣@Test("One is two")
626+
func `one is two`() {
627+
#expect(1 == 2)
628+
}4️⃣
624629
""",
625630
uri: uri
626631
)
@@ -634,7 +639,13 @@ final class DocumentTestDiscoveryTests: XCTestCase {
634639
label: "One is two",
635640
style: TestStyle.swiftTesting,
636641
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"])
637-
)
642+
),
643+
TestItem(
644+
id: "`one is two`()",
645+
label: "One is two",
646+
style: TestStyle.swiftTesting,
647+
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
648+
),
638649
]
639650
)
640651
}
@@ -692,6 +703,126 @@ final class DocumentTestDiscoveryTests: XCTestCase {
692703
)
693704
}
694705

706+
func testSwiftTestingTestWithRawIdentifiers() async throws {
707+
let testClient = try await TestSourceKitLSPClient()
708+
let uri = DocumentURI(for: .swift)
709+
710+
let positions = testClient.openDocument(
711+
"""
712+
import Testing
713+
714+
1️⃣struct MyTests {
715+
2️⃣@Test
716+
func `one two`(`a b`: Int, c: Int, `3`: Int, `$`: Int, `+1`: Int) {
717+
#expect(1 == 2)
718+
}3️⃣
719+
}4️⃣
720+
721+
extension MyTests {
722+
5️⃣@Test
723+
func `3four`() {
724+
#expect(2 == 3)
725+
}6️⃣
726+
// Don't include operators
727+
@Test
728+
func +() {
729+
#expect(2 == 3)
730+
}
731+
// This is invalid, but we'll pick it up as identifier.
732+
7️⃣@Test
733+
func `+`() {
734+
#expect(2 == 3)
735+
}8️⃣
736+
// Also invalid.
737+
9️⃣@Test
738+
func ``() {
739+
#expect(2 == 3)
740+
}🔟
741+
}
742+
""",
743+
uri: uri
744+
)
745+
746+
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
747+
XCTAssertEqual(
748+
tests,
749+
[
750+
TestItem(
751+
id: "MyTests",
752+
label: "MyTests",
753+
style: TestStyle.swiftTesting,
754+
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
755+
children: [
756+
TestItem(
757+
id: "MyTests/`one two`(`a b`:c:`3`:`$`:`+1`:)",
758+
label: "one two",
759+
style: TestStyle.swiftTesting,
760+
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
761+
),
762+
TestItem(
763+
id: "MyTests/`3four`()",
764+
label: "3four",
765+
style: TestStyle.swiftTesting,
766+
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
767+
),
768+
TestItem(
769+
id: "MyTests/`+`()",
770+
label: "+",
771+
style: TestStyle.swiftTesting,
772+
location: Location(uri: uri, range: positions["7️⃣"]..<positions["8️⃣"])
773+
),
774+
TestItem(
775+
id: "MyTests/``()",
776+
label: "``",
777+
style: TestStyle.swiftTesting,
778+
location: Location(uri: uri, range: positions["9️⃣"]..<positions["🔟"])
779+
),
780+
]
781+
)
782+
]
783+
)
784+
}
785+
786+
func testSwiftTestingTestWithSlashRawIdentifiers() async throws {
787+
let testClient = try await TestSourceKitLSPClient()
788+
let uri = DocumentURI(for: .swift)
789+
790+
let positions = testClient.openDocument(
791+
"""
792+
import Testing
793+
794+
1️⃣struct MyTests {
795+
2️⃣@Test
796+
func `x/y`() {
797+
#expect(1 == 2)
798+
}3️⃣
799+
}4️⃣
800+
""",
801+
uri: uri
802+
)
803+
804+
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
805+
XCTAssertEqual(
806+
tests,
807+
[
808+
TestItem(
809+
id: "MyTests",
810+
label: "MyTests",
811+
style: TestStyle.swiftTesting,
812+
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
813+
children: [
814+
TestItem(
815+
id: "MyTests/`x/y`()",
816+
label: "x/y",
817+
style: TestStyle.swiftTesting,
818+
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
819+
)
820+
]
821+
)
822+
]
823+
)
824+
}
825+
695826
func testSwiftTestingTestDisabledTest() async throws {
696827
let testClient = try await TestSourceKitLSPClient()
697828
let uri = DocumentURI(for: .swift)

0 commit comments

Comments
 (0)