From 966a7c2bb20e5eea1ed120c0b72c0cec5f68dcf1 Mon Sep 17 00:00:00 2001 From: MahdiBM Date: Thu, 11 Jul 2024 23:26:11 +0330 Subject: [PATCH] much better diagnostics --- .../EnumeratorMacroType.swift | 72 ++++++++++++++----- Sources/EnumeratorMacroImpl/MacroError.swift | 23 +++--- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift b/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift index d14d70c..4e7233d 100644 --- a/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift +++ b/Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift @@ -33,34 +33,74 @@ extension EnumeratorMacroType: MemberMacro { if exprList.isEmpty { throw MacroError.expectedAtLeastOneArgument } - let templates = try exprList.compactMap { element in + let templates = try exprList.compactMap { + element -> (template: String, syntax: StringLiteralExprSyntax) in guard let stringLiteral = element.expression.as(StringLiteralExprSyntax.self) else { throw MacroError.allArgumentsMustBeStringLiterals(violation: element.description) } - return stringLiteral + let template = stringLiteral .segments .formatted() .description + return (template, stringLiteral) } - let rendered = try templates.map { template in - try MustacheTemplate( - string: "{{%CONTENT_TYPE:TEXT}}\n" + template - ).render([ - "cases": cases - ]) + let rendered = templates.compactMap { + (template, syntax) -> (rendered: String, syntax: StringLiteralExprSyntax)? in + do { + let rendered = try MustacheTemplate( + string: "{{%CONTENT_TYPE:TEXT}}\n" + template + ).render([ + "cases": cases + ]) + return (rendered, syntax) + } catch { + let message: MacroError + let errorSyntax: SyntaxProtocol + if let parserError = error as? MustacheTemplate.ParserError { + message = .mustacheTemplateError( + message: String(describing: parserError.error) + ) + let segments = Array(syntax.segments) + let segmentIdx = parserError.context.lineNumber - 2 + if segmentIdx < segments.count { + let syntaxAtErrorLine = segments[segmentIdx] + errorSyntax = syntaxAtErrorLine + } else { + errorSyntax = syntax + } + } else { + message = .mustacheTemplateError( + message: String(describing: error) + ) + errorSyntax = syntax + } + context.diagnose( + Diagnostic( + node: errorSyntax, + message: message + ) + ) + return nil + } } - let syntaxes: [DeclSyntax] = rendered.flatMap { rendered in - SourceFileSyntax( + let syntaxes: [DeclSyntax] = rendered.compactMap { + (rendered, syntax) -> [DeclSyntax]? in + let decls = SourceFileSyntax( stringLiteral: rendered ).statements.compactMap { statement in DeclSyntax(statement.item) } - } - - let withErrors = syntaxes.filter(\.hasError) - guard withErrors.isEmpty else { - throw MacroError.renderedSyntaxesContainsErrors(withErrors.map(\.description)) - } + if let withError = decls.first(where: \.hasError) { + context.diagnose( + Diagnostic( + node: syntax, + message: MacroError.renderedSyntaxContainsErrors(withError.description) + ) + ) + return nil + } + return decls + }.flatMap { $0 } return syntaxes } diff --git a/Sources/EnumeratorMacroImpl/MacroError.swift b/Sources/EnumeratorMacroImpl/MacroError.swift index 82fc722..c1f183b 100644 --- a/Sources/EnumeratorMacroImpl/MacroError.swift +++ b/Sources/EnumeratorMacroImpl/MacroError.swift @@ -7,23 +7,28 @@ enum MacroError: Error, CustomStringConvertible { case unacceptableArguments case expectedAtLeastOneArgument case allArgumentsMustBeStringLiterals(violation: String) - case renderedSyntaxesContainsErrors([String]) + case renderedSyntaxContainsErrors(String) + case couldNotFindLocationOfNode(syntax: String) + case mustacheTemplateError(message: String) var description: String { switch self { case .isNotEnum: - return "Only enums are supported" + "Only enums are supported" case .macroDeclarationHasNoArguments: - return "The macro declaration needs to have at least 1 StringLiteral argument" + "The macro declaration needs to have at least 1 StringLiteral argument" case .unacceptableArguments: - return "The arguments passed to the macro were unacceptable" + "The arguments passed to the macro were unacceptable" case .expectedAtLeastOneArgument: - return "At least one argument of type StaticString is required" + "At least one argument of type StaticString is required" case let .allArgumentsMustBeStringLiterals(violation): - return "All arguments must be string literals, but found: \(violation)" - case let .renderedSyntaxesContainsErrors(syntaxes): - let syntaxes = syntaxes.joined(separator: "\n\(String(repeating: "-", count: 20))\n") - return "Some rendered syntaxes contain errors:\n\(syntaxes)" + "All arguments must be string literals, but found: \(violation)" + case let .renderedSyntaxContainsErrors(syntax): + "Rendered syntax contains errors:\n\(syntax)" + case let .couldNotFindLocationOfNode(syntax): + "Could not find location of node for syntax:\n\(syntax)" + case let .mustacheTemplateError(message): + "Error while rendering the template: \(message)" } } }