diff --git a/Sources/TestingMacros/SuiteDeclarationMacro.swift b/Sources/TestingMacros/SuiteDeclarationMacro.swift index 5658123a0..a79255bc8 100644 --- a/Sources/TestingMacros/SuiteDeclarationMacro.swift +++ b/Sources/TestingMacros/SuiteDeclarationMacro.swift @@ -63,7 +63,11 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { diagnostics += diagnoseIssuesWithLexicalContext(context.lexicalContext, containing: declaration, attribute: suiteAttribute) diagnostics += diagnoseIssuesWithLexicalContext(declaration, containing: declaration, attribute: suiteAttribute) - // Suites inheriting from XCTestCase are not supported. + // Suites inheriting from XCTestCase are not supported. This check is + // duplicated in TestDeclarationMacro but is not part of + // diagnoseIssuesWithLexicalContext() because it doesn't need to recurse + // across the entire lexical context list, just the innermost type + // declaration. if let declaration = declaration.asProtocol((any DeclGroupSyntax).self), declaration.inherits(fromTypeNamed: "XCTestCase", inModuleNamed: "XCTest") { diagnostics.append(.xcTestCaseNotSupported(declaration, whenUsing: suiteAttribute)) diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 1f9025f08..334cc1dac 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -59,6 +59,16 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { // Check if the lexical context is appropriate for a suite or test. diagnostics += diagnoseIssuesWithLexicalContext(context.lexicalContext, containing: declaration, attribute: testAttribute) + // Suites inheriting from XCTestCase are not supported. We are a bit + // conservative here in this check and only check the immediate context. + // Presumably, if there's an intermediate lexical context that is *not* a + // type declaration, then it must be a function or closure (disallowed + // elsewhere) and thus the test function is not a member of any type. + if let containingTypeDecl = context.lexicalContext.first?.asProtocol((any DeclGroupSyntax).self), + containingTypeDecl.inherits(fromTypeNamed: "XCTestCase", inModuleNamed: "XCTest") { + diagnostics.append(.containingNodeUnsupported(containingTypeDecl, whenUsing: testAttribute, on: declaration)) + } + // Only one @Test attribute is supported. let suiteAttributes = function.attributes(named: "Test") if suiteAttributes.count > 1 { diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index 6330d3fcf..fffa06664 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -82,6 +82,10 @@ struct TestDeclarationMacroTests { "Attribute 'Suite' cannot be applied to a subclass of 'XCTestCase'", "@Suite final class C: XCTest.XCTestCase {}": "Attribute 'Suite' cannot be applied to a subclass of 'XCTestCase'", + "final class C: XCTestCase { @Test func f() {} }": + "Attribute 'Test' cannot be applied to a function within class 'C'", + "final class C: XCTest.XCTestCase { @Test func f() {} }": + "Attribute 'Test' cannot be applied to a function within class 'C'", // Unsupported inheritance "@Suite protocol P {}":