Skip to content

Support member access in tags #1191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 32 additions & 25 deletions Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,18 @@ struct TestingAttributeData {
return false
}
}.flatMap(\.arguments)
.compactMap { $0.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue }
.compactMap {
if let memberAccess = $0.expression.as(MemberAccessExprSyntax.self) {
var components = memberAccess.components[...]
if components.starts(with: ["Testing", "Tag"]) {
components = components.dropFirst(2)
} else if components.starts(with: ["Tag"]) {
components = components.dropFirst(1)
}
return components.joined(separator: ".")
}
return nil
}

self.isDisabled = traitArguments.lazy
.compactMap { $0.as(FunctionCallExprSyntax.self) }
Expand Down Expand Up @@ -328,36 +339,32 @@ fileprivate extension AttributeSyntax {
}

fileprivate extension MemberAccessExprSyntax {
/// The base name of this instance, i.e. the string value of `base` joined
/// with any preceding base names.
/// The fully-qualified name of this instance (subject to available
/// information.)
///
/// For example, if this instance represents the expression `x.y.z(123)`,
/// the value of this property is `"x.y"`. If the value of `base` is `nil`,
/// the value of this property is also `nil`.
var baseName: String? {
if let declReferenceExpr = base?.as(DeclReferenceExprSyntax.self) {
return declReferenceExpr.baseName.text
} else if let baseMemberAccessExpr = base?.as(MemberAccessExprSyntax.self) {
if let baseBaseName = baseMemberAccessExpr.baseName {
return "\(baseBaseName).\(baseMemberAccessExpr.declName.baseName.text)"
}
return baseMemberAccessExpr.declName.baseName.text
}

return nil
/// The value of this property are all the components of the based name
/// name joined together with `.`.
var fullyQualifiedName: String {
components.joined(separator: ".")
}

/// The fully-qualified name of this instance (subject to available
/// The name components of this instance (subject to available
/// information.)
///
/// The value of this property is this instance's `baseName` property joined
/// with its `name` property. For example, if this instance represents the
/// expression `x.y.z(123)`, the value of this property is `"x.y.z"`.
var fullyQualifiedName: String {
if let baseName {
return "\(baseName).\(declName.baseName.text)"
/// The value of this property is this base name of this instance,
/// i.e. the string value of `base` preceeded with any preceding base names
/// and followed by its `name` property.
///
/// For example, if this instance represents
/// the expression `x.y.z(123)`, the value of this property is
/// `["x", "y", "z"]`.
var components: [String] {
if let declReferenceExpr = base?.as(DeclReferenceExprSyntax.self) {
return [declReferenceExpr.baseName.text, declName.baseName.text]
} else if let baseMemberAccessExpr = base?.as(MemberAccessExprSyntax.self) {
return baseMemberAccessExpr.components + [declName.baseName.text]
}
return declName.baseName.text
return [declName.baseName.text]
}
}

Expand Down
70 changes: 66 additions & 4 deletions Tests/SourceKitLSPTests/DocumentTestDiscoveryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -593,9 +593,9 @@ final class DocumentTestDiscoveryTests: XCTestCase {
"""
import Testing

1️⃣@Suite(.tags("Suites"))
1️⃣@Suite(.tags(.green))
struct MyTests {
2️⃣@Test(.tags("one", "two"))
2️⃣@Test(.tags(.red, .blue))
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
Expand All @@ -622,10 +622,72 @@ final class DocumentTestDiscoveryTests: XCTestCase {
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
children: [],
tags: [TestTag(id: "one"), TestTag(id: "two")]
tags: [TestTag(id: "red"), TestTag(id: "blue")]
)
],
tags: [TestTag(id: "Suites")]
tags: [TestTag(id: "green")]
)
]
)
}

func testSwiftTestingTestWithCustomTags() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI.for(.swift)

let positions = testClient.openDocument(
"""
import Testing

extension Tag {
@Tag static var suite: Self
@Tag static var foo: Self
@Tag static var bar: Self
@Tag static var baz: Self

struct Nested {
@Tag static var foo: Tag
}
}

1️⃣@Suite(.tags(.suite))
struct MyTests {
2️⃣@Test(.tags(.foo, Nested.foo, Testing.Tag.bar, Tag.baz))
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)

let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
disabled: false,
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
disabled: false,
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
children: [],
tags: [
TestTag(id: "foo"),
TestTag(id: "Nested.foo"),
TestTag(id: "bar"),
TestTag(id: "baz"),
]
)
],
tags: [TestTag(id: "suite")]
)
]
)
Expand Down