Skip to content

Commit 35fe4aa

Browse files
committed
More robust fully qualified name resolution
Add a `components` property to MemberAccessExprSyntax that provides all the base names and the member's name as an array. When resolving swift-testing Tags check if they start with Tag or Testing.Tag and drop that from the name.
1 parent def5285 commit 35fe4aa

File tree

2 files changed

+85
-27
lines changed

2 files changed

+85
-27
lines changed

Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,15 @@ struct TestingAttributeData {
9797
}.flatMap(\.arguments)
9898
.compactMap {
9999
if let stringLiteral = $0.expression.as(StringLiteralExprSyntax.self) {
100-
return stringLiteral.representedLiteralValue
100+
return stringLiteral.representedLiteralValue
101101
} else if let memberAccess = $0.expression.as(MemberAccessExprSyntax.self) {
102-
let baseName = (memberAccess.baseName.map { "\($0)." } ?? "").replacing(#/Tag\./#, with: "")
103-
return "\(baseName)\(memberAccess.declName.baseName.text)"
102+
var components = memberAccess.components[...]
103+
if components.starts(with: ["Testing", "Tag"]) {
104+
components = components.dropFirst(2)
105+
} else if components.starts(with: ["Tag"]) {
106+
components = components.dropFirst(1)
107+
}
108+
return components.joined(separator: ".")
104109
}
105110
return nil
106111
}
@@ -336,36 +341,34 @@ fileprivate extension AttributeSyntax {
336341
}
337342

338343
fileprivate extension MemberAccessExprSyntax {
339-
/// The base name of this instance, i.e. the string value of `base` joined
340-
/// with any preceding base names.
344+
/// The fully-qualified name of this instance (subject to available
345+
/// information.)
341346
///
342-
/// For example, if this instance represents the expression `x.y.z(123)`,
343-
/// the value of this property is `"x.y"`. If the value of `base` is `nil`,
344-
/// the value of this property is also `nil`.
345-
var baseName: String? {
346-
if let declReferenceExpr = base?.as(DeclReferenceExprSyntax.self) {
347-
return declReferenceExpr.baseName.text
348-
} else if let baseMemberAccessExpr = base?.as(MemberAccessExprSyntax.self) {
349-
if let baseBaseName = baseMemberAccessExpr.baseName {
350-
return "\(baseBaseName).\(baseMemberAccessExpr.declName.baseName.text)"
351-
}
352-
return baseMemberAccessExpr.declName.baseName.text
353-
}
354-
355-
return nil
347+
/// The value of this property are all the components of the based name
348+
/// name joined together with `.`.
349+
var fullyQualifiedName: String {
350+
components.joined(separator: ".")
356351
}
357352

358-
/// The fully-qualified name of this instance (subject to available
353+
/// The name components of this instance (subject to available
359354
/// information.)
360355
///
361-
/// The value of this property is this instance's `baseName` property joined
362-
/// with its `name` property. For example, if this instance represents the
363-
/// expression `x.y.z(123)`, the value of this property is `"x.y.z"`.
364-
var fullyQualifiedName: String {
365-
if let baseName {
366-
return "\(baseName).\(declName.baseName.text)"
356+
/// The value of this property is this base name of this instance,
357+
/// i.e. the string value of `base` preceeded with any preceding base names
358+
/// and followed by its `name` property.
359+
///
360+
/// For example, if this instance represents
361+
/// the expression `x.y.z(123)`, the value of this property is
362+
/// `["x", "y", "z"]`.
363+
var components: [String] {
364+
if let declReferenceExpr = base?.as(DeclReferenceExprSyntax.self) {
365+
return [declReferenceExpr.baseName.text, declName.baseName.text]
366+
} else if let baseMemberAccessExpr = base?.as(MemberAccessExprSyntax.self) {
367+
let baseBaseNames = baseMemberAccessExpr.components.dropLast()
368+
return baseBaseNames + [baseMemberAccessExpr.declName.baseName.text, declName.baseName.text]
367369
}
368-
return declName.baseName.text
370+
371+
return [declName.baseName.text]
369372
}
370373
}
371374

Tests/SourceKitLSPTests/DocumentTestDiscoveryTests.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,61 @@ final class DocumentTestDiscoveryTests: XCTestCase {
631631
)
632632
}
633633

634+
func testSwiftTestingTestWithCustomTags() async throws {
635+
let testClient = try await TestSourceKitLSPClient()
636+
let uri = DocumentURI.for(.swift)
637+
638+
let positions = testClient.openDocument(
639+
"""
640+
import Testing
641+
642+
extension Tag {
643+
@Tag static var foo: Self
644+
@Tag static var bar: Self
645+
646+
struct Nested {
647+
@Tag static var foo: Tag
648+
}
649+
}
650+
651+
1️⃣@Suite(.tags("Suites"))
652+
struct MyTests {
653+
2️⃣@Test(.tags(.foo, Nested.foo, Testing.Tag.bar))
654+
func oneIsTwo() {
655+
#expect(1 == 2)
656+
}3️⃣
657+
}4️⃣
658+
""",
659+
uri: uri
660+
)
661+
662+
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
663+
XCTAssertEqual(
664+
tests,
665+
[
666+
TestItem(
667+
id: "MyTests",
668+
label: "MyTests",
669+
disabled: false,
670+
style: TestStyle.swiftTesting,
671+
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
672+
children: [
673+
TestItem(
674+
id: "MyTests/oneIsTwo()",
675+
label: "oneIsTwo()",
676+
disabled: false,
677+
style: TestStyle.swiftTesting,
678+
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
679+
children: [],
680+
tags: [TestTag(id: "foo"), TestTag(id: "Nested.foo"), TestTag(id: "bar")]
681+
)
682+
],
683+
tags: [TestTag(id: "Suites")]
684+
)
685+
]
686+
)
687+
}
688+
634689
func testSwiftTestingTestsWithExtension() async throws {
635690
let testClient = try await TestSourceKitLSPClient()
636691
let uri = DocumentURI.for(.swift)

0 commit comments

Comments
 (0)