diff --git a/Tests/MacroTestingTests/MacroExamples/MetaEnumMacro.swift b/Tests/MacroTestingTests/MacroExamples/MetaEnumMacro.swift index 22ac266..5688534 100644 --- a/Tests/MacroTestingTests/MacroExamples/MetaEnumMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/MetaEnumMacro.swift @@ -26,9 +26,19 @@ public struct MetaEnumMacro { ) throws { guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { throw DiagnosticsError(diagnostics: [ - CaseMacroDiagnostic.notAnEnum(declaration).diagnose(at: Syntax(node)) + CaseMacroDiagnostic.notAnEnum(declaration).diagnose(at: Syntax(declaration.introducer)) ]) } + + var seenCaseNames: Set = [] + for name in enumDecl.caseElements.map(\.name) { + defer { seenCaseNames.insert(name.text) } + guard !seenCaseNames.contains(name.text) else { + throw DiagnosticsError(diagnostics: [ + CaseMacroDiagnostic.overloadedCase.diagnose(at: Syntax(name)) + ]) + } + } parentTypeName = enumDecl.name.with(\.trailingTrivia, []) @@ -105,29 +115,33 @@ extension EnumDeclSyntax { } enum CaseMacroDiagnostic { + case overloadedCase case notAnEnum(DeclGroupSyntax) } extension CaseMacroDiagnostic: DiagnosticMessage { var message: String { switch self { + case .overloadedCase: + "'@MetaEnum' cannot be applied to enums with overloaded case names." case .notAnEnum(let decl): - return - "'@MetaEnum' can only be attached to an enum, not \(decl.descriptiveDeclKind(withArticle: true))" + "'@MetaEnum' can only be attached to an enum, not \(decl.descriptiveDeclKind(withArticle: true))" } } var diagnosticID: MessageID { switch self { + case .overloadedCase: + MessageID(domain: "MetaEnumDiagnostic", id: "overloadedCase") case .notAnEnum: - return MessageID(domain: "MetaEnumDiagnostic", id: "notAnEnum") + MessageID(domain: "MetaEnumDiagnostic", id: "notAnEnum") } } var severity: DiagnosticSeverity { switch self { - case .notAnEnum: - return .error + case .overloadedCase: .error + case .notAnEnum: .error } } @@ -138,21 +152,8 @@ extension CaseMacroDiagnostic: DiagnosticMessage { extension DeclGroupSyntax { func descriptiveDeclKind(withArticle article: Bool = false) -> String { - switch self { - case is ActorDeclSyntax: - return article ? "an actor" : "actor" - case is ClassDeclSyntax: - return article ? "a class" : "class" - case is ExtensionDeclSyntax: - return article ? "an extension" : "extension" - case is ProtocolDeclSyntax: - return article ? "a protocol" : "protocol" - case is StructDeclSyntax: - return article ? "a struct" : "struct" - case is EnumDeclSyntax: - return article ? "an enum" : "enum" - default: - fatalError("Unknown DeclGroupSyntax") - } + let introducerText = introducer.text + let prefix = article ? ["a", "e", "i", "o", "u"].contains(introducerText.first!) ? "an " : "a " : "" + return prefix + introducerText } } diff --git a/Tests/MacroTestingTests/MetaEnumMacroTests.swift b/Tests/MacroTestingTests/MetaEnumMacroTests.swift index f12c0d4..c5c1443 100644 --- a/Tests/MacroTestingTests/MetaEnumMacroTests.swift +++ b/Tests/MacroTestingTests/MetaEnumMacroTests.swift @@ -97,8 +97,8 @@ final class MetaEnumMacroTests: BaseTestCase { } diagnostics: { """ @MetaEnum struct Cell { - ┬──────── - ╰─ 🛑 '@MetaEnum' can only be attached to an enum, not a struct + ┬───── + ╰─ 🛑 '@MetaEnum' can only be attached to an enum, not a struct let integer: Int let text: String let boolean: Bool @@ -106,4 +106,42 @@ final class MetaEnumMacroTests: BaseTestCase { """ } } + + func testDuplicateCaseName() { + assertMacro { + """ + @MetaEnum enum Foo { + case bar(int: Int) + case bar(string: String) + } + """ + } diagnostics: { + """ + @MetaEnum enum Foo { + case bar(int: Int) + case bar(string: String) + ┬── + ╰─ 🛑 '@MetaEnum' cannot be applied to enums with overloaded case names. + } + """ + } + } + + func testOverloadedCaseName_SingleLine() { + assertMacro { + """ + @MetaEnum enum Foo { + case bar(int: Int), bar(string: String) + } + """ + } diagnostics: { + """ + @MetaEnum enum Foo { + case bar(int: Int), bar(string: String) + ┬── + ╰─ 🛑 '@MetaEnum' cannot be applied to enums with overloaded case names. + } + """ + } + } }