From 4fdb96e6d8527495a811209bafb3183c543192bc Mon Sep 17 00:00:00 2001 From: Joe Newton Date: Fri, 10 Nov 2023 08:52:02 -0500 Subject: [PATCH 1/4] Fixed up two issues that were discovered 1. When a diagnostic has multiple fix its the annotated string didn't insert newlines between fix-its 2. When a syntax has the same node offset as one of its children, replacing the child with a fix-it would *always* replace the outermost node --- Sources/MacroTesting/AssertMacro.swift | 4 +- .../DiagnosticsFormatter.swift | 9 ++-- ...iagnosticsAndFixitsEmitterMacroTests.swift | 42 +++++++++++++++ .../DiagnosticsAndFixitsEmitterMacro.swift | 51 +++++++++++++++++++ .../MacroExamples/OptionSetMacro.swift | 41 +++++++++++---- .../OptionSetMacroTests.swift | 16 ++++-- 6 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 Tests/MacroTestingTests/DiagnosticsAndFixitsEmitterMacroTests.swift create mode 100644 Tests/MacroTestingTests/MacroExamples/DiagnosticsAndFixitsEmitterMacro.swift diff --git a/Sources/MacroTesting/AssertMacro.swift b/Sources/MacroTesting/AssertMacro.swift index 57fce29..4cc9bfe 100644 --- a/Sources/MacroTesting/AssertMacro.swift +++ b/Sources/MacroTesting/AssertMacro.swift @@ -602,7 +602,9 @@ private class FixItApplier: SyntaxRewriter { .location(for: oldNode.position, anchoredAt: oldNode, fileName: "") .offset if node.position.utf8Offset == offset { - return newNode + if node.kind == oldNode.kind { + return newNode + } } default: break diff --git a/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift b/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift index be2b7a6..e17918a 100644 --- a/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift +++ b/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift @@ -324,12 +324,15 @@ struct DiagnosticsFormatter { for diag in diags.dropLast(1) { annotatedSource.append("\(preMessage)├─ \(colorizeIfRequested(diag.diagMessage))\n") for fixIt in diag.fixIts { - annotatedSource.append("\(preMessage)│ ✏️ \(fixIt.message.message)") + annotatedSource.append("\(preMessage)│ ✏️ \(fixIt.message.message)\n") } } annotatedSource.append("\(preMessage)╰─ \(colorizeIfRequested(diags.last!.diagMessage))\n") - for fixIt in diags.last!.fixIts { - annotatedSource.append("\(preMessage) ✏️ \(fixIt.message.message)") + for fixIt in diags.last!.fixIts.dropLast(1) { + annotatedSource.append("\(preMessage) ✏️ \(fixIt.message.message)\n") + } + if !diags.last!.fixIts.isEmpty { + annotatedSource.append("\(preMessage) ✏️ \(diags.last!.fixIts.last!.message.message)\n") } } diff --git a/Tests/MacroTestingTests/DiagnosticsAndFixitsEmitterMacroTests.swift b/Tests/MacroTestingTests/DiagnosticsAndFixitsEmitterMacroTests.swift new file mode 100644 index 0000000..bc67c36 --- /dev/null +++ b/Tests/MacroTestingTests/DiagnosticsAndFixitsEmitterMacroTests.swift @@ -0,0 +1,42 @@ +import MacroTesting +import XCTest + +final class DiagnosticsAndFixitsEmitterMacroTests: BaseTestCase { + override func invokeTest() { + withMacroTesting(macros: [DiagnosticsAndFixitsEmitterMacro.self]) { + super.invokeTest() + } + } + + func testExpansionEmitsDiagnosticsAndFixits() { + assertMacro { + """ + @DiagnosticsAndFixitsEmitter + struct FooBar { + let foo: Foo + let bar: Bar + } + """ + } diagnostics: { + """ + @DiagnosticsAndFixitsEmitter + ┬────────────────────────── + ├─ ⚠️ This is the first diagnostic. + │ ✏️ This is the first fix-it. + │ ✏️ This is the second fix-it. + ╰─ ℹ️ This is the second diagnostic, it's a note. + struct FooBar { + let foo: Foo + let bar: Bar + } + """ + } expansion: { + """ + struct FooBar { + let foo: Foo + let bar: Bar + } + """ + } + } +} diff --git a/Tests/MacroTestingTests/MacroExamples/DiagnosticsAndFixitsEmitterMacro.swift b/Tests/MacroTestingTests/MacroExamples/DiagnosticsAndFixitsEmitterMacro.swift new file mode 100644 index 0000000..6bf47f7 --- /dev/null +++ b/Tests/MacroTestingTests/MacroExamples/DiagnosticsAndFixitsEmitterMacro.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros +import SwiftDiagnostics + +/// Emits two diagnostics, the first of which is a warning and has two fix-its, and +/// the second is a note and has no fix-its. +public enum DiagnosticsAndFixitsEmitterMacro: MemberMacro { + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + let firstFixIt = FixIt(message: SimpleDiagnosticMessage(message: "This is the first fix-it.", + diagnosticID: MessageID(domain: "domain", id: "fixit1"), + severity: .error), + changes: [ + .replace(oldNode: Syntax(node), newNode: Syntax(node)) // no-op + ]) + let secondFixIt = FixIt(message: SimpleDiagnosticMessage(message: "This is the second fix-it.", + diagnosticID: MessageID(domain: "domain", id: "fixit2"), + severity: .error), + changes: [ + .replace(oldNode: Syntax(node), newNode: Syntax(node)) // no-op + ]) + + context.diagnose(Diagnostic(node: node.attributeName, + message: SimpleDiagnosticMessage(message: "This is the first diagnostic.", + diagnosticID: MessageID(domain: "domain", id: "diagnostic2"), + severity: .warning), + fixIts: [firstFixIt, secondFixIt])) + context.diagnose(Diagnostic(node: node.attributeName, + message: SimpleDiagnosticMessage(message: "This is the second diagnostic, it's a note.", + diagnosticID: MessageID(domain: "domain", id: "diagnostic2"), + severity: .note))) + + return [] + } +} diff --git a/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift b/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift index f34c9e8..4c4c13f 100644 --- a/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift @@ -79,7 +79,8 @@ public struct OptionSetMacro { static func decodeExpansion( of attribute: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, - in context: some MacroExpansionContext + in context: some MacroExpansionContext, + emitDiagnostics: Bool ) -> (StructDeclSyntax, EnumDeclSyntax, TypeSyntax)? { // Determine the name of the options enum. let optionsEnumName: String @@ -91,9 +92,11 @@ public struct OptionSetMacro { stringLiteral.segments.count == 1, case let .stringSegment(optionsEnumNameString)? = stringLiteral.segments.first else { - context.diagnose( - OptionSetMacroDiagnostic.requiresStringLiteral(optionsEnumNameArgumentLabel).diagnose( - at: optionEnumNameArg.expression)) + if emitDiagnostics { + context.diagnose( + OptionSetMacroDiagnostic.requiresStringLiteral(optionsEnumNameArgumentLabel).diagnose( + at: optionEnumNameArg.expression)) + } return nil } @@ -104,7 +107,21 @@ public struct OptionSetMacro { // Only apply to structs. guard let structDecl = decl.as(StructDeclSyntax.self) else { - context.diagnose(OptionSetMacroDiagnostic.requiresStruct.diagnose(at: decl)) + if emitDiagnostics { + // Offer a fix-it to remove the attribute + let fixit = FixIt(message: SimpleDiagnosticMessage(message: "Remove '@OptionSet' attribute", + diagnosticID: MessageID(domain: "Swift", id: "OptionSet.fixit"), + severity: .error), + changes: [ + // Doesn't account for the fact that there may be other attributes present, but for + // this unit test it should be fine. + .replace(oldNode: Syntax(decl.attributes), newNode: Syntax(AttributeListSyntax())) + ]) + + context.diagnose(Diagnostic(node: decl, + message: OptionSetMacroDiagnostic.requiresStruct, + fixIt: fixit)) + } return nil } @@ -120,8 +137,10 @@ public struct OptionSetMacro { return nil }).first else { - context.diagnose( - OptionSetMacroDiagnostic.requiresOptionsEnum(optionsEnumName).diagnose(at: decl)) + if emitDiagnostics { + context.diagnose( + OptionSetMacroDiagnostic.requiresOptionsEnum(optionsEnumName).diagnose(at: decl)) + } return nil } @@ -131,7 +150,9 @@ public struct OptionSetMacro { .genericArgumentClause, let rawType = genericArgs.arguments.first?.argument else { - context.diagnose(OptionSetMacroDiagnostic.requiresOptionsEnumRawType.diagnose(at: attribute)) + if emitDiagnostics { + context.diagnose(OptionSetMacroDiagnostic.requiresOptionsEnumRawType.diagnose(at: attribute)) + } return nil } @@ -148,7 +169,7 @@ extension OptionSetMacro: ExtensionMacro { in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { // Decode the expansion arguments. - guard let (structDecl, _, _) = decodeExpansion(of: node, attachedTo: declaration, in: context) + guard let (structDecl, _, _) = decodeExpansion(of: node, attachedTo: declaration, in: context, emitDiagnostics: false) else { return [] } @@ -174,7 +195,7 @@ extension OptionSetMacro: MemberMacro { ) throws -> [DeclSyntax] { // Decode the expansion arguments. guard - let (_, optionsEnum, rawType) = decodeExpansion(of: attribute, attachedTo: decl, in: context) + let (_, optionsEnum, rawType) = decodeExpansion(of: attribute, attachedTo: decl, in: context, emitDiagnostics: true) else { return [] } diff --git a/Tests/MacroTestingTests/OptionSetMacroTests.swift b/Tests/MacroTestingTests/OptionSetMacroTests.swift index 05cac84..adeed88 100644 --- a/Tests/MacroTestingTests/OptionSetMacroTests.swift +++ b/Tests/MacroTestingTests/OptionSetMacroTests.swift @@ -120,8 +120,20 @@ final class OptionSetMacroTests: BaseTestCase { } diagnostics: { """ @MyOptionSet - ├─ 🛑 'OptionSet' macro can only be applied to a struct ╰─ 🛑 'OptionSet' macro can only be applied to a struct + ✏️ Remove '@OptionSet' attribute + enum Animal { + case dog + } + """ + } fixes: { + """ + enum Animal { + case dog + } + """ + } expansion: { + """ enum Animal { case dog } @@ -141,7 +153,6 @@ final class OptionSetMacroTests: BaseTestCase { } diagnostics: { """ @MyOptionSet - ├─ 🛑 'OptionSet' macro requires nested options enum 'Options' ╰─ 🛑 'OptionSet' macro requires nested options enum 'Options' struct ShippingOptions { static let express: ShippingOptions = [.nextDay, .secondDay] @@ -165,7 +176,6 @@ final class OptionSetMacroTests: BaseTestCase { """ @MyOptionSet ┬─────────── - ├─ 🛑 'OptionSet' macro requires a raw type ╰─ 🛑 'OptionSet' macro requires a raw type struct ShippingOptions { private enum Options: Int { From a6658b12dc0fb713ca94cddf64aa079b9c771470 Mon Sep 17 00:00:00 2001 From: Joe Newton Date: Fri, 10 Nov 2023 09:08:52 -0500 Subject: [PATCH 2/4] Fixed up formatting --- .../MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift b/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift index e17918a..99b88e3 100644 --- a/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift +++ b/Sources/MacroTesting/SwiftDiagnostics/DiagnosticsFormatter.swift @@ -328,12 +328,9 @@ struct DiagnosticsFormatter { } } annotatedSource.append("\(preMessage)╰─ \(colorizeIfRequested(diags.last!.diagMessage))\n") - for fixIt in diags.last!.fixIts.dropLast(1) { + for fixIt in diags.last!.fixIts { annotatedSource.append("\(preMessage) ✏️ \(fixIt.message.message)\n") } - if !diags.last!.fixIts.isEmpty { - annotatedSource.append("\(preMessage) ✏️ \(diags.last!.fixIts.last!.message.message)\n") - } } // Add suffix text. From c318532f1a0156653c92cdd8c3910fff139f70a4 Mon Sep 17 00:00:00 2001 From: Joe Newton Date: Sat, 11 Nov 2023 11:51:26 -0500 Subject: [PATCH 3/4] Updates per code review suggestions --- .../ActorOnlyMacroTests.swift | 42 ++++++++++++ .../MacroExamples/ActorOnlyMacro.swift | 64 +++++++++++++++++++ .../MacroExamples/OptionSetMacro.swift | 41 +++--------- .../OptionSetMacroTests.swift | 16 +---- 4 files changed, 119 insertions(+), 44 deletions(-) create mode 100644 Tests/MacroTestingTests/ActorOnlyMacroTests.swift create mode 100644 Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift diff --git a/Tests/MacroTestingTests/ActorOnlyMacroTests.swift b/Tests/MacroTestingTests/ActorOnlyMacroTests.swift new file mode 100644 index 0000000..436bb5d --- /dev/null +++ b/Tests/MacroTestingTests/ActorOnlyMacroTests.swift @@ -0,0 +1,42 @@ +import MacroTesting +import XCTest + +final class ActorOnlyMacroTests: BaseTestCase { + override func invokeTest() { + withMacroTesting(macros: ["ActorOnly": ActorOnlyMacro.self]) { + super.invokeTest() + } + } + + func testExpansionOnStruct() { + assertMacro { + """ + @ActorOnly + struct MyStruct { + + } + """ + } diagnostics: { + """ + @ActorOnly + ╰─ 🛑 'ActorOnly' macro can only be applied to an actor + ✏️ Remove '@ActorOnly' attribute + struct MyStruct { + + } + """ + } fixes: { + """ + struct MyStruct { + + } + """ + } expansion: { + """ + struct MyStruct { + + } + """ + } + } +} diff --git a/Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift b/Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift new file mode 100644 index 0000000..57f20d2 --- /dev/null +++ b/Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftDiagnostics +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +enum ActorOnlyMacroDiagnostic { + case requiresActor +} + +extension ActorOnlyMacroDiagnostic: DiagnosticMessage { + + var message: String { + switch self { + case .requiresActor: + return "'ActorOnly' macro can only be applied to an actor" + } + } + + var severity: DiagnosticSeverity { .error } + + var diagnosticID: MessageID { + MessageID(domain: "Swift", id: "ActorOnly.\(self)") + } +} + +public struct ActorOnlyMacro: MemberMacro { + public static func expansion( + of attribute: AttributeSyntax, + providingMembersOf decl: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + // Only apply to actors. + guard decl.is(ActorDeclSyntax.self) else { + // Offer a fix-it to remove the attribute + let fixit = FixIt(message: SimpleDiagnosticMessage(message: "Remove '@ActorOnly' attribute", + diagnosticID: MessageID(domain: "Swift", id: "OptionSet.fixit"), + severity: .error), + changes: [ + // Doesn't account for the fact that there may be other attributes present, but for + // this unit test it should be fine. + .replace(oldNode: Syntax(decl.attributes), newNode: Syntax(AttributeListSyntax())) + ]) + + context.diagnose(Diagnostic(node: decl, + message: ActorOnlyMacroDiagnostic.requiresActor, + fixIt: fixit)) + return [] + } + + return ["static let actorType: Actor.Type = Self.self"] + } +} diff --git a/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift b/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift index 4c4c13f..f34c9e8 100644 --- a/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/OptionSetMacro.swift @@ -79,8 +79,7 @@ public struct OptionSetMacro { static func decodeExpansion( of attribute: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, - in context: some MacroExpansionContext, - emitDiagnostics: Bool + in context: some MacroExpansionContext ) -> (StructDeclSyntax, EnumDeclSyntax, TypeSyntax)? { // Determine the name of the options enum. let optionsEnumName: String @@ -92,11 +91,9 @@ public struct OptionSetMacro { stringLiteral.segments.count == 1, case let .stringSegment(optionsEnumNameString)? = stringLiteral.segments.first else { - if emitDiagnostics { - context.diagnose( - OptionSetMacroDiagnostic.requiresStringLiteral(optionsEnumNameArgumentLabel).diagnose( - at: optionEnumNameArg.expression)) - } + context.diagnose( + OptionSetMacroDiagnostic.requiresStringLiteral(optionsEnumNameArgumentLabel).diagnose( + at: optionEnumNameArg.expression)) return nil } @@ -107,21 +104,7 @@ public struct OptionSetMacro { // Only apply to structs. guard let structDecl = decl.as(StructDeclSyntax.self) else { - if emitDiagnostics { - // Offer a fix-it to remove the attribute - let fixit = FixIt(message: SimpleDiagnosticMessage(message: "Remove '@OptionSet' attribute", - diagnosticID: MessageID(domain: "Swift", id: "OptionSet.fixit"), - severity: .error), - changes: [ - // Doesn't account for the fact that there may be other attributes present, but for - // this unit test it should be fine. - .replace(oldNode: Syntax(decl.attributes), newNode: Syntax(AttributeListSyntax())) - ]) - - context.diagnose(Diagnostic(node: decl, - message: OptionSetMacroDiagnostic.requiresStruct, - fixIt: fixit)) - } + context.diagnose(OptionSetMacroDiagnostic.requiresStruct.diagnose(at: decl)) return nil } @@ -137,10 +120,8 @@ public struct OptionSetMacro { return nil }).first else { - if emitDiagnostics { - context.diagnose( - OptionSetMacroDiagnostic.requiresOptionsEnum(optionsEnumName).diagnose(at: decl)) - } + context.diagnose( + OptionSetMacroDiagnostic.requiresOptionsEnum(optionsEnumName).diagnose(at: decl)) return nil } @@ -150,9 +131,7 @@ public struct OptionSetMacro { .genericArgumentClause, let rawType = genericArgs.arguments.first?.argument else { - if emitDiagnostics { - context.diagnose(OptionSetMacroDiagnostic.requiresOptionsEnumRawType.diagnose(at: attribute)) - } + context.diagnose(OptionSetMacroDiagnostic.requiresOptionsEnumRawType.diagnose(at: attribute)) return nil } @@ -169,7 +148,7 @@ extension OptionSetMacro: ExtensionMacro { in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { // Decode the expansion arguments. - guard let (structDecl, _, _) = decodeExpansion(of: node, attachedTo: declaration, in: context, emitDiagnostics: false) + guard let (structDecl, _, _) = decodeExpansion(of: node, attachedTo: declaration, in: context) else { return [] } @@ -195,7 +174,7 @@ extension OptionSetMacro: MemberMacro { ) throws -> [DeclSyntax] { // Decode the expansion arguments. guard - let (_, optionsEnum, rawType) = decodeExpansion(of: attribute, attachedTo: decl, in: context, emitDiagnostics: true) + let (_, optionsEnum, rawType) = decodeExpansion(of: attribute, attachedTo: decl, in: context) else { return [] } diff --git a/Tests/MacroTestingTests/OptionSetMacroTests.swift b/Tests/MacroTestingTests/OptionSetMacroTests.swift index adeed88..05cac84 100644 --- a/Tests/MacroTestingTests/OptionSetMacroTests.swift +++ b/Tests/MacroTestingTests/OptionSetMacroTests.swift @@ -120,20 +120,8 @@ final class OptionSetMacroTests: BaseTestCase { } diagnostics: { """ @MyOptionSet + ├─ 🛑 'OptionSet' macro can only be applied to a struct ╰─ 🛑 'OptionSet' macro can only be applied to a struct - ✏️ Remove '@OptionSet' attribute - enum Animal { - case dog - } - """ - } fixes: { - """ - enum Animal { - case dog - } - """ - } expansion: { - """ enum Animal { case dog } @@ -153,6 +141,7 @@ final class OptionSetMacroTests: BaseTestCase { } diagnostics: { """ @MyOptionSet + ├─ 🛑 'OptionSet' macro requires nested options enum 'Options' ╰─ 🛑 'OptionSet' macro requires nested options enum 'Options' struct ShippingOptions { static let express: ShippingOptions = [.nextDay, .secondDay] @@ -176,6 +165,7 @@ final class OptionSetMacroTests: BaseTestCase { """ @MyOptionSet ┬─────────── + ├─ 🛑 'OptionSet' macro requires a raw type ╰─ 🛑 'OptionSet' macro requires a raw type struct ShippingOptions { private enum Options: Int { From c81248fd8e8276d65aa9bc7abbf165bac82a410b Mon Sep 17 00:00:00 2001 From: Joe Newton Date: Tue, 14 Nov 2023 15:59:10 -0500 Subject: [PATCH 4/4] Additional updates per code review suggestions --- Sources/MacroTesting/AssertMacro.swift | 4 +- .../ActorOnlyMacroTests.swift | 42 ------------ .../MacroExamples/ActorOnlyMacro.swift | 64 ------------------- 3 files changed, 1 insertion(+), 109 deletions(-) delete mode 100644 Tests/MacroTestingTests/ActorOnlyMacroTests.swift delete mode 100644 Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift diff --git a/Sources/MacroTesting/AssertMacro.swift b/Sources/MacroTesting/AssertMacro.swift index 4cc9bfe..57fce29 100644 --- a/Sources/MacroTesting/AssertMacro.swift +++ b/Sources/MacroTesting/AssertMacro.swift @@ -602,9 +602,7 @@ private class FixItApplier: SyntaxRewriter { .location(for: oldNode.position, anchoredAt: oldNode, fileName: "") .offset if node.position.utf8Offset == offset { - if node.kind == oldNode.kind { - return newNode - } + return newNode } default: break diff --git a/Tests/MacroTestingTests/ActorOnlyMacroTests.swift b/Tests/MacroTestingTests/ActorOnlyMacroTests.swift deleted file mode 100644 index 436bb5d..0000000 --- a/Tests/MacroTestingTests/ActorOnlyMacroTests.swift +++ /dev/null @@ -1,42 +0,0 @@ -import MacroTesting -import XCTest - -final class ActorOnlyMacroTests: BaseTestCase { - override func invokeTest() { - withMacroTesting(macros: ["ActorOnly": ActorOnlyMacro.self]) { - super.invokeTest() - } - } - - func testExpansionOnStruct() { - assertMacro { - """ - @ActorOnly - struct MyStruct { - - } - """ - } diagnostics: { - """ - @ActorOnly - ╰─ 🛑 'ActorOnly' macro can only be applied to an actor - ✏️ Remove '@ActorOnly' attribute - struct MyStruct { - - } - """ - } fixes: { - """ - struct MyStruct { - - } - """ - } expansion: { - """ - struct MyStruct { - - } - """ - } - } -} diff --git a/Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift b/Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift deleted file mode 100644 index 57f20d2..0000000 --- a/Tests/MacroTestingTests/MacroExamples/ActorOnlyMacro.swift +++ /dev/null @@ -1,64 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftDiagnostics -import SwiftSyntax -import SwiftSyntaxBuilder -import SwiftSyntaxMacros - -enum ActorOnlyMacroDiagnostic { - case requiresActor -} - -extension ActorOnlyMacroDiagnostic: DiagnosticMessage { - - var message: String { - switch self { - case .requiresActor: - return "'ActorOnly' macro can only be applied to an actor" - } - } - - var severity: DiagnosticSeverity { .error } - - var diagnosticID: MessageID { - MessageID(domain: "Swift", id: "ActorOnly.\(self)") - } -} - -public struct ActorOnlyMacro: MemberMacro { - public static func expansion( - of attribute: AttributeSyntax, - providingMembersOf decl: some DeclGroupSyntax, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - // Only apply to actors. - guard decl.is(ActorDeclSyntax.self) else { - // Offer a fix-it to remove the attribute - let fixit = FixIt(message: SimpleDiagnosticMessage(message: "Remove '@ActorOnly' attribute", - diagnosticID: MessageID(domain: "Swift", id: "OptionSet.fixit"), - severity: .error), - changes: [ - // Doesn't account for the fact that there may be other attributes present, but for - // this unit test it should be fine. - .replace(oldNode: Syntax(decl.attributes), newNode: Syntax(AttributeListSyntax())) - ]) - - context.diagnose(Diagnostic(node: decl, - message: ActorOnlyMacroDiagnostic.requiresActor, - fixIt: fixit)) - return [] - } - - return ["static let actorType: Actor.Type = Self.self"] - } -}