diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index f1cfd665f..77b2b174e 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -66,17 +66,19 @@ struct AttributeInfo { /// The traits applied to the attribute, if any. var traits = [ExprSyntax]() + /// Test arguments passed to a parameterized test function, if any. + /// + /// When non-`nil`, the value of this property is an array beginning with the + /// argument passed to this attribute for the parameter labeled `arguments:` + /// followed by all of the remaining, unlabeled arguments. + var testFunctionArguments: [Argument]? + /// Whether or not this attribute specifies arguments to the associated test /// function. var hasFunctionArguments: Bool { - otherArguments.lazy - .compactMap(\.label?.tokenKind) - .contains(.identifier("arguments")) + testFunctionArguments != nil } - /// Additional arguments passed to the attribute, if any. - var otherArguments = [Argument]() - /// The source location of the attribute. /// /// When parsing, the testing library uses the start of the attribute's name @@ -98,6 +100,7 @@ struct AttributeInfo { init(byParsing attribute: AttributeSyntax, on declaration: some SyntaxProtocol, in context: some MacroExpansionContext) { self.attribute = attribute + var nonDisplayNameArguments: [Argument] = [] if let arguments = attribute.arguments, case let .argumentList(argumentList) = arguments { // If the first argument is an unlabelled string literal, it's the display // name of the test or suite. If it's anything else, including a nil @@ -106,11 +109,11 @@ struct AttributeInfo { let firstArgumentHasLabel = (firstArgument.label != nil) if !firstArgumentHasLabel, let stringLiteral = firstArgument.expression.as(StringLiteralExprSyntax.self) { displayName = stringLiteral - otherArguments = argumentList.dropFirst().map(Argument.init) + nonDisplayNameArguments = argumentList.dropFirst().map(Argument.init) } else if !firstArgumentHasLabel, firstArgument.expression.is(NilLiteralExprSyntax.self) { - otherArguments = argumentList.dropFirst().map(Argument.init) + nonDisplayNameArguments = argumentList.dropFirst().map(Argument.init) } else { - otherArguments = argumentList.map(Argument.init) + nonDisplayNameArguments = argumentList.map(Argument.init) } } } @@ -119,7 +122,7 @@ struct AttributeInfo { // See _SelfRemover for more information. Rewriting a syntax tree discards // location information from the copy, so only invoke the rewriter if the // `Self` keyword is present somewhere. - otherArguments = otherArguments.map { argument in + nonDisplayNameArguments = nonDisplayNameArguments.map { argument in var expr = argument.expression if argument.expression.tokens(viewMode: .sourceAccurate).map(\.tokenKind).contains(.keyword(.Self)) { let selfRemover = _SelfRemover(in: context) @@ -131,15 +134,14 @@ struct AttributeInfo { // Look for any traits in the remaining arguments and slice them off. Traits // are the remaining unlabelled arguments. The first labelled argument (if // present) is the start of subsequent context-specific arguments. - if !otherArguments.isEmpty { - if let labelledArgumentIndex = otherArguments.firstIndex(where: { $0.label != nil }) { + if !nonDisplayNameArguments.isEmpty { + if let labelledArgumentIndex = nonDisplayNameArguments.firstIndex(where: { $0.label != nil }) { // There is an argument with a label, so splice there. - traits = otherArguments[otherArguments.startIndex ..< labelledArgumentIndex].map(\.expression) - otherArguments = Array(otherArguments[labelledArgumentIndex...]) + traits = nonDisplayNameArguments[..