generated from dankinsoid/iOSLibraryTemplate
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2ee6c2a
commit 9698d86
Showing
8 changed files
with
267 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#if canImport(SwiftCompilerPlugin) | ||
import SwiftCompilerPlugin | ||
import SwiftSyntax | ||
import SwiftSyntaxMacros | ||
|
||
@main | ||
struct OpenAPIDescriptionPlugin: CompilerPlugin { | ||
|
||
let providingMacros: [Macro.Type] = [ | ||
OpenAPIDescriptionMacro.self | ||
] | ||
} | ||
|
||
public struct OpenAPIDescriptionMacro: ExtensionMacro { | ||
|
||
public static func expansion( | ||
of node: AttributeSyntax, | ||
attachedTo declaration: some DeclGroupSyntax, | ||
providingExtensionsOf type: some TypeSyntaxProtocol, | ||
conformingTo protocols: [TypeSyntax], | ||
in context: some MacroExpansionContext | ||
) throws -> [ExtensionDeclSyntax] { | ||
let typeDoc = declaration | ||
.children(viewMode: .all) | ||
.compactMap(\.documentation) | ||
.first? | ||
.wrapped | ||
let varDocs = declaration.memberBlock.members.compactMap { member -> (String, String)? in | ||
guard | ||
let variable = member.decl.as(VariableDeclSyntax.self), | ||
variable.attributes.isEmpty, | ||
let doc = variable.documentation | ||
else { | ||
return nil | ||
} | ||
|
||
var name: String? | ||
for binding in variable.bindings { | ||
if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) { | ||
name = identifier.identifier.text | ||
} | ||
if let closure = binding.accessorBlock { | ||
guard | ||
let list = closure.accessors.as(AccessorDeclListSyntax.self), | ||
list.contains(where: \.accessorSpecifier.isWillSetOrDidSet) | ||
else { | ||
return nil | ||
} | ||
} | ||
} | ||
return name.map { ($0, doc) } | ||
} | ||
let varDocsModifiers = varDocs.map { | ||
"\n .add(for: \"\($0.0)\", \($0.1.wrapped))" | ||
} | ||
.joined() | ||
|
||
let sendableExtension: DeclSyntax = | ||
""" | ||
extension \(type.trimmed): OpenAPIDescriptable { | ||
public static var openAPIDescription: OpenAPIDescriptionType? { | ||
OpenAPIDescription<String>(\(raw: typeDoc ?? ""))\(raw: varDocsModifiers) | ||
} | ||
} | ||
""" | ||
|
||
guard let extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self) else { | ||
throw StringError("Failed to create extension declaration") | ||
} | ||
|
||
return [extensionDecl] | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Foundation | ||
|
||
struct StringError: LocalizedError { | ||
|
||
var errorDescription: String? | ||
|
||
init(_ errorDescription: String? = nil) { | ||
self.errorDescription = errorDescription | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#if canImport(SwiftCompilerPlugin) | ||
import SwiftCompilerPlugin | ||
import SwiftSyntax | ||
import SwiftSyntaxMacros | ||
|
||
extension TokenSyntax { | ||
|
||
var isWillSetOrDidSet: Bool { | ||
self == .keyword(.didSet) || self == .keyword(.willSet) | ||
} | ||
} | ||
|
||
extension SyntaxProtocol { | ||
|
||
var documentation: String? { | ||
leadingTrivia.documentation | ||
} | ||
} | ||
|
||
extension Trivia { | ||
|
||
var documentation: String? { | ||
let lines = compactMap { $0.documentation } | ||
guard lines.count > 1 else { return lines.first?.trimmingCharacters(in: .whitespaces) } | ||
|
||
let indentation = lines.compactMap { $0.firstIndex(where: { !$0.isWhitespace })?.utf16Offset(in: $0) } | ||
.min() ?? 0 | ||
|
||
return lines.map { | ||
guard $0.count > indentation else { return String($0) } | ||
return String($0.suffix($0.count - indentation)) | ||
}.joined(separator: "\\n") | ||
} | ||
} | ||
|
||
extension TriviaPiece { | ||
|
||
var documentation: String? { | ||
switch self { | ||
case let .docLineComment(comment): | ||
let startIndex = comment.index(comment.startIndex, offsetBy: 3) | ||
return String(comment.suffix(from: startIndex)) | ||
case let .lineComment(comment): | ||
let startIndex = comment.index(comment.startIndex, offsetBy: 2) | ||
return String(comment.suffix(from: startIndex)) | ||
case let .docBlockComment(comment): | ||
let startIndex = comment.index(comment.startIndex, offsetBy: 3) | ||
let endIndex = comment.index(comment.endIndex, offsetBy: -2) | ||
return String(comment[startIndex ..< endIndex]) | ||
case let .blockComment(comment): | ||
let startIndex = comment.index(comment.startIndex, offsetBy: 2) | ||
let endIndex = comment.index(comment.endIndex, offsetBy: -2) | ||
return String(comment[startIndex ..< endIndex]) | ||
default: | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
extension String { | ||
|
||
var wrapped: String { | ||
"#\"\(self)\"#" | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import Foundation | ||
import SwiftSyntaxMacros | ||
import SwiftSyntaxMacrosTestSupport | ||
import XCTest | ||
@testable import SwiftOpenAPIMacros | ||
@testable import SwiftOpenAPI | ||
|
||
let testMacros: [String: Macro.Type] = [ | ||
"OpenAPIAutoDescriptable": OpenAPIDescriptionMacro.self | ||
] | ||
|
||
final class OpenAPIDescriptionMacroTests: XCTestCase { | ||
|
||
func test_should_create_extension() { | ||
assertMacroExpansion( | ||
""" | ||
/// A person. | ||
@OpenAPIAutoDescriptable | ||
struct Person: Codable { | ||
/// The person's name. | ||
let name: String | ||
} | ||
""", | ||
expandedSource: """ | ||
/// A person. | ||
struct Person: Codable { | ||
/// The person's name. | ||
let name: String | ||
} | ||
extension Person: OpenAPIDescriptable { | ||
public static var openAPIDescription: OpenAPIDescriptionType? { | ||
OpenAPIDescription<String>(#"A person."#) | ||
.add(for: "name", #"The person's name."#) | ||
} | ||
} | ||
""", | ||
macros: testMacros | ||
) | ||
} | ||
|
||
func test_created_extension() { | ||
XCTAssertEqual( | ||
Person.openAPIDescription as? OpenAPIDescription<String>, | ||
OpenAPIDescription<String>("A person.") | ||
.add(for: "name", "The person's name.") | ||
) | ||
} | ||
} | ||
|
||
@OpenAPIAutoDescriptable | ||
/// A person. | ||
struct Person { | ||
|
||
/// The person's name. | ||
let name: String | ||
} |