diff --git a/Sources/EnumeratorMacro/Enumerator.swift b/Sources/EnumeratorMacro/Enumerator.swift index 5594afa..f7b815b 100644 --- a/Sources/EnumeratorMacro/Enumerator.swift +++ b/Sources/EnumeratorMacro/Enumerator.swift @@ -1,20 +1,5 @@ @attached(member, names: arbitrary) -public macro Enumerator(_ templates: StaticString...) = #externalMacro( +public macro Enumerator(_ templates: String...) = #externalMacro( module: "EnumeratorMacroImpl", type: "EnumeratorMacroType" ) - -@Enumerator(""" -var caseName: String { - switch self { - {{#cases}} - case .{{name}}: "{{name}}" - {{/cases}} - } -} -""") -enum TestEnum { - case a(val1: String, val2: Int) - case b - case testCase(testValue: String) -} diff --git a/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift b/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift index 93c06b7..13934ac 100644 --- a/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift +++ b/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift @@ -34,14 +34,27 @@ extension EnumeratorMacroType: MemberMacro { if exprList.isEmpty { throw MacroError.expectedAtLeastOneArgument } - let templates = try exprList.compactMap { - element -> (template: String, syntax: StringLiteralExprSyntax) in + let templates = exprList.compactMap { + element -> (template: String, syntax: StringLiteralExprSyntax)? in guard let stringLiteral = element.expression.as(StringLiteralExprSyntax.self) else { - throw MacroError.allArgumentsMustBeStringLiterals(violation: element.description) + context.diagnose( + Diagnostic( + node: element.expression, + message: MacroError.allArgumentsMustBeNonInterpolatedStringLiterals + ) + ) + return nil + } + for segment in stringLiteral.segments where !segment.is(StringSegmentSyntax.self) { + context.diagnose( + Diagnostic( + node: segment, + message: MacroError.allArgumentsMustBeNonInterpolatedStringLiterals + ) + ) + return nil } - let template = stringLiteral - .segments - .description + let template = stringLiteral.segments.description return (template, stringLiteral) } let rendered = templates.compactMap { diff --git a/Sources/EnumeratorMacroImpl/MacroError.swift b/Sources/EnumeratorMacroImpl/MacroError.swift index 9d9d6a9..218a9ee 100644 --- a/Sources/EnumeratorMacroImpl/MacroError.swift +++ b/Sources/EnumeratorMacroImpl/MacroError.swift @@ -6,7 +6,7 @@ enum MacroError: Error, CustomStringConvertible { case macroDeclarationHasNoArguments case unacceptableArguments case expectedAtLeastOneArgument - case allArgumentsMustBeStringLiterals(violation: String) + case allArgumentsMustBeNonInterpolatedStringLiterals case renderedSyntaxContainsErrors(String) case couldNotFindLocationOfNode(syntax: String) case mustacheTemplateError(message: String) @@ -23,8 +23,8 @@ enum MacroError: Error, CustomStringConvertible { "unacceptableArguments" case .expectedAtLeastOneArgument: "expectedAtLeastOneArgument" - case .allArgumentsMustBeStringLiterals: - "allArgumentsMustBeStringLiterals" + case .allArgumentsMustBeNonInterpolatedStringLiterals: + "allArgumentsMustBeNonInterpolatedStringLiterals" case .renderedSyntaxContainsErrors: "renderedSyntaxContainsErrors" case .couldNotFindLocationOfNode: @@ -48,8 +48,8 @@ enum MacroError: Error, CustomStringConvertible { "The arguments passed to the macro were unacceptable" case .expectedAtLeastOneArgument: "At least one argument of type StaticString is required" - case let .allArgumentsMustBeStringLiterals(violation): - "All arguments must be string literals, but found: \(violation)" + case .allArgumentsMustBeNonInterpolatedStringLiterals: + "All arguments must be non-interpolated string literals." case let .renderedSyntaxContainsErrors(syntax): "Rendered syntax contains errors:\n\(syntax)" case let .couldNotFindLocationOfNode(syntax): diff --git a/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift b/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift index 831171b..fd8b7a5 100644 --- a/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift +++ b/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift @@ -404,13 +404,52 @@ final class EnumeratorMacroTests: XCTestCase { diagnostics: [.init( id: .init( domain: "EnumeratorMacro.MacroError", - id: "allArgumentsMustBeStringLiterals" + id: "allArgumentsMustBeNonInterpolatedStringLiterals" ), message: """ - All arguments must be string literals, but found: myVariable + All arguments must be non-interpolated string literals. """, line: 1, - column: 1, + column: 13, + severity: .error + )], + macros: EnumeratorMacroEntryPoint.macros + ) + } + + func testDiagnosesStringInterpolationInMustacheTemplate() throws { + assertMacroExpansion( + #""" + @Enumerator(""" + enum Subtype: String { + {{#cases}} + case \(name) + {{/cases}} + } + """) + enum TestEnum { + case a(val1: String, Int) + case b + case testCase(testValue: String) + } + """#, + expandedSource: #""" + enum TestEnum { + case a(val1: String, Int) + case b + case testCase(testValue: String) + } + """#, + diagnostics: [.init( + id: .init( + domain: "EnumeratorMacro.MacroError", + id: "allArgumentsMustBeNonInterpolatedStringLiterals" + ), + message: """ + All arguments must be non-interpolated string literals. + """, + line: 4, + column: 10, severity: .error )], macros: EnumeratorMacroEntryPoint.macros