Skip to content

Commit 531f993

Browse files
committed
Infer a display name for tests and suites which have a raw identifier name consisting of a single token
1 parent 3ee5ec0 commit 531f993

File tree

5 files changed

+38
-13
lines changed

5 files changed

+38
-13
lines changed

Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import SwiftSyntax
1212
import SwiftSyntaxBuilder
1313
import SwiftSyntaxMacros
14+
import SwiftParser
1415

1516
extension FunctionDeclSyntax {
1617
/// Whether or not this function a `static` or `class` function.
@@ -38,7 +39,7 @@ extension FunctionDeclSyntax {
3839
/// colons.
3940
var completeName: DeclReferenceExprSyntax {
4041
func possiblyRaw(_ token: TokenSyntax) -> TokenSyntax {
41-
if let rawIdentifier = token.rawIdentifier {
42+
if let rawIdentifier = token.rawIdentifier, !rawIdentifier.isValidSwiftIdentifier(for: .memberAccess) {
4243
return .identifier("`\(rawIdentifier)`")
4344
}
4445
return .identifier(token.textWithoutBackticks)

Sources/TestingMacros/Support/Additions/TokenSyntaxAdditions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ extension TokenSyntax {
3535
/// token, or `nil` if it does not represent one.
3636
var rawIdentifier: String? {
3737
let (textWithoutBackticks, backticksRemoved) = _textWithoutBackticks
38-
if backticksRemoved, !textWithoutBackticks.isValidSwiftIdentifier(for: .memberAccess) {
38+
if backticksRemoved {
3939
return textWithoutBackticks
4040
}
4141

Sources/TestingMacros/Support/AttributeDiscovery.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import SwiftSyntax
1212
import SwiftSyntaxBuilder
1313
import SwiftSyntaxMacros
14+
import SwiftParser
1415

1516
/// A syntax rewriter that removes leading `Self.` tokens from member access
1617
/// expressions in a syntax tree.
@@ -138,11 +139,17 @@ struct AttributeInfo {
138139
}
139140
}
140141

141-
// Disallow an explicit display name for tests and suites with raw
142-
// identifier names as it's redundant and potentially confusing.
142+
// If this declaration has a raw identifier name, implicitly use the content
143+
// of that raw identifier as its display name.
143144
if let namedDecl = declaration.asProtocol((any NamedDeclSyntax).self),
144145
let rawIdentifier = namedDecl.name.rawIdentifier {
145-
if let displayName, let displayNameArgument {
146+
// Disallow having an explicit display name for tests and suites with raw
147+
// identifier names as it's redundant and potentially confusing. An
148+
// exception to this rule is if the content of the raw identifier is not a
149+
// valid identifier on its own. For example:
150+
//
151+
// @Test("...") func `subscript`() { ... }
152+
if let displayName, let displayNameArgument, !rawIdentifier.isValidSwiftIdentifier(for: .memberAccess) {
146153
context.diagnose(.declaration(namedDecl, hasExtraneousDisplayName: displayName, fromArgument: displayNameArgument, using: attribute))
147154
} else {
148155
displayName = StringLiteralExprSyntax(content: rawIdentifier)

Tests/TestingMacrosTests/TestDeclarationMacroTests.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,19 +263,26 @@ struct TestDeclarationMacroTests {
263263
}
264264
}
265265

266+
@Test("No diagnostics are emitted for tests with both a raw identifier name and explicit display name when deemed non-redundant", arguments: [
267+
#"@Test("Class") func `class`()"#,
268+
#"@Test("Struct") func `struct`()"#,
269+
#"@Test("Subscript") func `subscript`()"#,
270+
])
271+
func nonRedundantRawIdentifierDisplayName(input: String) throws {
272+
let (_, diagnostics) = try parse(input)
273+
#expect(diagnostics.isEmpty)
274+
}
275+
266276
@Test("Raw identifier is detected")
267277
func rawIdentifier() {
268-
#expect(TokenSyntax.identifier("`hello`").rawIdentifier == nil)
269-
#expect(TokenSyntax.identifier("`helloworld`").rawIdentifier == nil)
270-
#expect(TokenSyntax.identifier("`hélloworld`").rawIdentifier == nil)
271-
#expect(TokenSyntax.identifier("`hello_world`").rawIdentifier == nil)
278+
#expect(TokenSyntax.identifier("hello").rawIdentifier == nil)
279+
#expect(TokenSyntax.identifier("`hello").rawIdentifier == nil)
280+
#expect(TokenSyntax.identifier("hello`").rawIdentifier == nil)
281+
#expect(TokenSyntax.identifier("hélloworld").rawIdentifier == nil)
282+
#expect(TokenSyntax.identifier("hello_world").rawIdentifier == nil)
272283
#expect(TokenSyntax.identifier("`hello world`").rawIdentifier != nil)
273284
#expect(TokenSyntax.identifier("`hello/world`").rawIdentifier != nil)
274285
#expect(TokenSyntax.identifier("`hello\tworld`").rawIdentifier != nil)
275-
276-
#expect(TokenSyntax.identifier("`class`").rawIdentifier == nil)
277-
#expect(TokenSyntax.identifier("`struct`").rawIdentifier == nil)
278-
#expect(TokenSyntax.identifier("`class struct`").rawIdentifier != nil)
279286
}
280287

281288
@Test("Raw function name components")

Tests/TestingTests/MiscellaneousTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ private import Foundation
2828

2929
@Sendable func freeSyncFunctionParameterized2(_ i: Int, _ j: String) {}
3030

31+
#if compiler(>=6.2) && hasFeature(RawIdentifiers)
32+
@Test(.hidden, arguments: [0]) func `ValidSingleCapitalizedToken`(someLengthyParameterName: Int) {}
33+
#endif
34+
3135
// This type ensures the parser can correctly infer that f() is a member
3236
// function even though @Test is preceded by another attribute or is embedded in
3337
// a #if statement.
@@ -320,6 +324,12 @@ struct MiscellaneousTests {
320324
func `Test with raw identifier and raw identifier parameter labels can compile`(`argument name` i: Int) {
321325
#expect(i == 0)
322326
}
327+
328+
@Test func singleValidTokenRawIdentifiers() async throws {
329+
let test = try #require(await Test.all.first { $0.name.contains("ValidSingleCapitalizedToken") })
330+
#expect(test.name == "ValidSingleCapitalizedToken(someLengthyParameterName:)")
331+
#expect(test.displayName == "ValidSingleCapitalizedToken")
332+
}
323333
#endif
324334

325335
@Test("Free functions are runnable")

0 commit comments

Comments
 (0)