diff --git a/Sources/Testing/Test+Macro.swift b/Sources/Testing/Test+Macro.swift index 0fb29562d..86fb42c14 100644 --- a/Sources/Testing/Test+Macro.swift +++ b/Sources/Testing/Test+Macro.swift @@ -499,12 +499,24 @@ extension Test { /// - Note: This macro has compile-time effects _only_ and should not affect a /// compiled test target. /// -/// - Warning: This macro is used to implement other macros declared by the testing -/// library. Do not use it directly. +/// - Warning: This macro is used to implement other macros declared by the +/// testing library. Do not use it directly. @attached(peer) public macro __testing( semantics arguments: _const String... ) = #externalMacro(module: "TestingMacros", type: "PragmaMacro") +/// A macro used similarly to `#warning()` but in a position where only an +/// attribute is valid. +/// +/// - Parameters: +/// - message: A string to emit as a warning. +/// +/// - Warning: This macro is used to implement other macros declared by the +/// testing library. Do not use it directly. +@attached(peer) public macro __testing( + warning message: _const String +) = #externalMacro(module: "TestingMacros", type: "PragmaMacro") + // MARK: - Helper functions /// A function that abstracts away whether or not the `try` keyword is needed on diff --git a/Sources/TestingMacros/PragmaMacro.swift b/Sources/TestingMacros/PragmaMacro.swift index 48027b213..783440764 100644 --- a/Sources/TestingMacros/PragmaMacro.swift +++ b/Sources/TestingMacros/PragmaMacro.swift @@ -17,7 +17,9 @@ public import SwiftSyntaxMacros /// /// - `@__testing(semantics: "nomacrowarnings")`: suppress warning diagnostics /// generated by macros. (The implementation of this use case is held in trust -/// at ``MacroExpansionContext/areWarningsSuppressed``. +/// at ``MacroExpansionContext/areWarningsSuppressed``.) +/// - `@__testing(warning: "...")`: emits `"..."` as a diagnostic message +/// attributed to the node to which the attribute is attached. /// /// This type is used to implement the `@__testing` attribute macro. Do not use /// it directly. @@ -27,6 +29,17 @@ public struct PragmaMacro: PeerMacro, Sendable { providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { + if case let .argumentList(arguments) = node.arguments, + arguments.first?.label?.textWithoutBackticks == "warning" { + let targetNode = Syntax(declaration) + let messages = arguments + .map(\.expression) + .compactMap { $0.as(StringLiteralExprSyntax.self) } + .compactMap(\.representedLiteralValue) + .map { DiagnosticMessage(syntax: targetNode, message: $0, severity: .warning) } + context.diagnose(messages) + } + return [] } diff --git a/Tests/TestingMacrosTests/PragmaMacroTests.swift b/Tests/TestingMacrosTests/PragmaMacroTests.swift index 9e85419da..0d430c036 100644 --- a/Tests/TestingMacrosTests/PragmaMacroTests.swift +++ b/Tests/TestingMacrosTests/PragmaMacroTests.swift @@ -18,12 +18,25 @@ import SwiftSyntax struct PragmaMacroTests { @Test func findSemantics() throws { let node = """ - @Testing.__testing(semantics: "abc123") - @__testing(semantics: "def456") - let x = 0 + @Testing.__testing(semantics: "abc123") + @__testing(semantics: "def456") + let x = 0 """ as DeclSyntax let nodeWithAttributes = try #require(node.asProtocol((any WithAttributesSyntax).self)) let semantics = semantics(of: nodeWithAttributes) #expect(semantics == ["abc123", "def456"]) } + + @Test func warningGenerated() throws { + let sourceCode = """ + @__testing(warning: "abc123") + let x = 0 + """ + + let (_, diagnostics) = try parse(sourceCode) + #expect(diagnostics.count == 1) + #expect(diagnostics[0].message == "abc123") + #expect(diagnostics[0].diagMessage.severity == .warning) + #expect(diagnostics[0].node.is(VariableDeclSyntax.self)) + } } diff --git a/Tests/TestingMacrosTests/TestSupport/Parse.swift b/Tests/TestingMacrosTests/TestSupport/Parse.swift index e6b36e3b2..ecff8de58 100644 --- a/Tests/TestingMacrosTests/TestSupport/Parse.swift +++ b/Tests/TestingMacrosTests/TestSupport/Parse.swift @@ -30,6 +30,7 @@ fileprivate let allMacros: [String: any Macro.Type] = [ "Suite": SuiteDeclarationMacro.self, "Test": TestDeclarationMacro.self, "Tag": TagMacro.self, + "__testing": PragmaMacro.self, ] func parse(_ sourceCode: String, activeMacros activeMacroNames: [String] = [], removeWhitespace: Bool = false) throws -> (sourceCode: String, diagnostics: [Diagnostic]) { diff --git a/Tests/TestingTests/MiscellaneousTests.swift b/Tests/TestingTests/MiscellaneousTests.swift index 3c987a9ad..a8cd56a7b 100644 --- a/Tests/TestingTests/MiscellaneousTests.swift +++ b/Tests/TestingTests/MiscellaneousTests.swift @@ -598,6 +598,8 @@ struct MiscellaneousTests { @_section("swift5_tests") #elseif os(Windows) @_section(".sw5test$B") +#else + @__testing(warning: "Platform-specific implementation missing: test content section name unavailable") #endif @_used private static let record: __TestContentRecord = (