Skip to content
This repository was archived by the owner on Dec 11, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Sources/GRDBMacros/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import GRDB
conformances: FetchableRecord, PersistableRecord,
names: named(init(row:)), named(encode(to:)), named(Columns), named(databaseSelection)
)
@attached(
member,
names: named(init(row:))
)
public macro DatabaseRecord() = #externalMacro(module: "GRDBMacrosPlugin", type: "DatabaseRecordMacro")

@attached(peer)
Expand Down
46 changes: 46 additions & 0 deletions Sources/GRDBMacrosPlugin/DatabaseRecordMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ extension DatabaseRecordMacro: ExtensionMacro {
}

let isPublic = declaration.modifiers.map(\.name.tokenKind).contains(.keyword(.public))
let isClass = declaration.is(ClassDeclSyntax.self)

let access = if isPublic {
TokenSyntax(.keyword(.public), trailingTrivia: [.spaces(1)], presence: .present)
Expand All @@ -72,6 +73,7 @@ extension DatabaseRecordMacro: ExtensionMacro {

let needsExtImpl: [ExtensionWithBody] = [
.init(name: fetchableType) {
isClass ? "" :
"""
\(raw: access)init(row: \(raw: qualified("Row"))) {
\(raw: args.map(\.fetchableDecl.trimmedDescription).joined(separator: "\n"))
Expand Down Expand Up @@ -126,6 +128,50 @@ extension DatabaseRecordMacro: ExtensionMacro {
}
}

extension DatabaseRecordMacro: MemberMacro {
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
if declaration.is(StructDeclSyntax.self) {
return []
}
guard let declaration = declaration.as(ClassDeclSyntax.self) else {
throw Error.onlyClassOrStructs
}

let isPublic = declaration.modifiers.map(\.name.tokenKind).contains(.keyword(.public))

let access = if isPublic {
TokenSyntax(.keyword(.public), trailingTrivia: [.spaces(1)], presence: .present)
} else {
TokenSyntax(.stringSegment(""), presence: .missing)
}

let inherited = declaration.inheritanceClause?.inheritedTypes ?? []

if inherited.map(\.type).contains(where: {
$0.trimmedDescription == fetchableType ||
$0.trimmedDescription == qualified(DeclSyntax(stringLiteral: fetchableType)).trimmedDescription
}) {
return []
}

let args: [MemberColumn] = declaration.memberBlock.members
.compactMap(MemberColumn.init)

return [
"""
\(raw: access)required init(row: \(raw: qualified("Row"))) {
\(raw: args.map(\.fetchableDecl.trimmedDescription).joined(separator: "\n"))
}
"""
]
}
}

// MARK: - MemberColumn + init & properties

extension DatabaseRecordMacro.MemberColumn {
Expand Down
48 changes: 48 additions & 0 deletions Tests/GRDBMacrosTests/GRDBMacrosTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,52 @@ final class GRDBMacrosTests: XCTestCase {
"""
}
}

func testMacroForClass() throws {
assertMacro {
"""
@DatabaseRecord
class Author {
var id: Int64?
var countryCode: String?
}
"""
} expansion: {
"""
class Author {
var id: Int64?
var countryCode: String?

required init(row: GRDB.Row) {
self.id = row[Columns.id]
self.countryCode = row[Columns.countryCode]
}
}

extension Author: GRDB.FetchableRecord {
}

extension Author: GRDB.PersistableRecord {
func encode(to container: inout GRDB.PersistenceContainer) throws {
container[Columns.id] = id
container[Columns.countryCode] = countryCode
}
}

extension Author {
enum Columns {
static let id = GRDB.Column("id")
static let countryCode = GRDB.Column("countryCode")
}
}

extension Author {
static let databaseSelection: [any GRDB.SQLSelectable] = [
Columns.id,
Columns.countryCode
]
}
"""
}
}
}