diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index 3d95df294..29de508b2 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -138,14 +138,17 @@ struct AttributeInfo { } } - // Disallow an explicit display name for tests and suites with raw - // identifier names as it's redundant and potentially confusing. + // If this declaration's name is surrounded by backticks, it may be an + // escaped or raw identifier. If it has an explicitly-specified display name + // string, honor that, otherwise use the content of the backtick-enclosed + // name as the display name. if let namedDecl = declaration.asProtocol((any NamedDeclSyntax).self), - let rawIdentifier = namedDecl.name.rawIdentifier { + case let nameWithoutBackticks = namedDecl.name.textWithoutBackticks, + nameWithoutBackticks != namedDecl.name.text { if let displayName, let displayNameArgument { context.diagnose(.declaration(namedDecl, hasExtraneousDisplayName: displayName, fromArgument: displayNameArgument, using: attribute)) } else { - displayName = StringLiteralExprSyntax(content: rawIdentifier) + displayName = StringLiteralExprSyntax(content: nameWithoutBackticks) } } diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index 6c04eb9eb..bd9034254 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -263,19 +263,60 @@ struct TestDeclarationMacroTests { } } + @Test("Warning diagnostics which include fix-its emitted on API misuse", arguments: [ + #"@Test("Subscript") func `subscript`()"#: + ( + message: "Attribute 'Test' specifies display name 'Subscript' for function with implicit display name 'subscript'", + fixIts: [ + ExpectedFixIt( + message: "Remove 'Subscript'", + changes: [.replace(oldSourceCode: #""Subscript""#, newSourceCode: "")] + ), + ExpectedFixIt( + message: "Rename 'subscript'", + changes: [.replace(oldSourceCode: "`subscript`", newSourceCode: "\(EditorPlaceholderExprSyntax("name"))")] + ), + ] + ), + ]) + func apiMisuseWarningsIncludingFixIts(input: String, expectedDiagnostic: (message: String, fixIts: [ExpectedFixIt])) throws { + let (_, diagnostics) = try parse(input) + + #expect(diagnostics.count == 1) + let diagnostic = try #require(diagnostics.first) + #expect(diagnostic.diagMessage.severity == .warning) + #expect(diagnostic.message == expectedDiagnostic.message) + + try #require(diagnostic.fixIts.count == expectedDiagnostic.fixIts.count) + for (fixIt, expectedFixIt) in zip(diagnostic.fixIts, expectedDiagnostic.fixIts) { + #expect(fixIt.message.message == expectedFixIt.message) + + try #require(fixIt.changes.count == expectedFixIt.changes.count) + for (change, expectedChange) in zip(fixIt.changes, expectedFixIt.changes) { + switch (change, expectedChange) { + case let (.replace(oldNode, newNode), .replace(expectedOldSourceCode, expectedNewSourceCode)): + let oldSourceCode = String(describing: oldNode.formatted()) + #expect(oldSourceCode == expectedOldSourceCode) + + let newSourceCode = String(describing: newNode.formatted()) + #expect(newSourceCode == expectedNewSourceCode) + default: + Issue.record("Change \(change) differs from expected change \(expectedChange)") + } + } + } + } + @Test("Raw identifier is detected") func rawIdentifier() { - #expect(TokenSyntax.identifier("`hello`").rawIdentifier == nil) - #expect(TokenSyntax.identifier("`helloworld`").rawIdentifier == nil) - #expect(TokenSyntax.identifier("`hélloworld`").rawIdentifier == nil) - #expect(TokenSyntax.identifier("`hello_world`").rawIdentifier == nil) + #expect(TokenSyntax.identifier("hello").rawIdentifier == nil) + #expect(TokenSyntax.identifier("`hello").rawIdentifier == nil) + #expect(TokenSyntax.identifier("hello`").rawIdentifier == nil) + #expect(TokenSyntax.identifier("hélloworld").rawIdentifier == nil) + #expect(TokenSyntax.identifier("hello_world").rawIdentifier == nil) #expect(TokenSyntax.identifier("`hello world`").rawIdentifier != nil) #expect(TokenSyntax.identifier("`hello/world`").rawIdentifier != nil) #expect(TokenSyntax.identifier("`hello\tworld`").rawIdentifier != nil) - - #expect(TokenSyntax.identifier("`class`").rawIdentifier == nil) - #expect(TokenSyntax.identifier("`struct`").rawIdentifier == nil) - #expect(TokenSyntax.identifier("`class struct`").rawIdentifier != nil) } @Test("Raw function name components") diff --git a/Tests/TestingTests/MiscellaneousTests.swift b/Tests/TestingTests/MiscellaneousTests.swift index 6a65fb658..259ef4766 100644 --- a/Tests/TestingTests/MiscellaneousTests.swift +++ b/Tests/TestingTests/MiscellaneousTests.swift @@ -28,6 +28,10 @@ private import Foundation @Sendable func freeSyncFunctionParameterized2(_ i: Int, _ j: String) {} +#if compiler(>=6.2) && hasFeature(RawIdentifiers) +@Test(.hidden, arguments: [0]) func `ValidSingleCapitalizedToken`(someLengthyParameterName: Int) {} +#endif + // This type ensures the parser can correctly infer that f() is a member // function even though @Test is preceded by another attribute or is embedded in // a #if statement. @@ -320,6 +324,12 @@ struct MiscellaneousTests { func `Test with raw identifier and raw identifier parameter labels can compile`(`argument name` i: Int) { #expect(i == 0) } + + @Test func singleValidTokenRawIdentifiers() async throws { + let test = try #require(await Test.all.first { $0.name.contains("ValidSingleCapitalizedToken") }) + #expect(test.name == "ValidSingleCapitalizedToken(someLengthyParameterName:)") + #expect(test.displayName == "ValidSingleCapitalizedToken") + } #endif @Test("Free functions are runnable") diff --git a/Tests/TestingTests/Support/GraphTests.swift b/Tests/TestingTests/Support/GraphTests.swift index 5f956dd60..9de7e28f3 100644 --- a/Tests/TestingTests/Support/GraphTests.swift +++ b/Tests/TestingTests/Support/GraphTests.swift @@ -53,8 +53,7 @@ struct GraphTests { #expect(graph.subgraph(at: "C2", "C3")?.value == 2468) } - @Test("subscript([K]) operator") - func `subscript`() { + @Test func `subscript([K]) operator`() { let graph = Graph(value: 123, children: [ "C1": Graph(value: 456), "C2": Graph(value: 789, children: [