Skip to content

Commit

Permalink
fix: automockable macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-T-Dev committed Nov 6, 2024
1 parent 065f4eb commit c2633b1
Show file tree
Hide file tree
Showing 16 changed files with 622 additions and 422 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [0.2.2] - 2024-11-06

### Fixed

- Allow Automackable to support more cases

# [0.2.1] - 2024-10-23

### Fixed

- Fix unsuported optionals to StringOrInt and StringOrDouble macros

# [0.2.0] - 2024-10-23

### Added

- StringToDouble macro
-
# [0.1.1] - 2024-10-21

### Fixed
Expand Down
1 change: 0 additions & 1 deletion Sources/SageSwiftKit/CodableMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Copyright © 2024 Sage.
// All Rights Reserved.


import Foundation

@attached(peer)
Expand Down
1 change: 0 additions & 1 deletion Sources/SageSwiftKit/JsonMockable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import Foundation


@attached(member, names: arbitrary)
public macro JsonMockable(
keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy,
Expand Down
1 change: 0 additions & 1 deletion Sources/SageSwiftKit/SageSwiftKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ public macro CustomHashable(parameters: [String]) = #externalMacro(module: "Sage
@attached(extension, conformances: Equatable, names: arbitrary)
public macro CustomEquatable(parameters: [String]) = #externalMacro(module: "SageSwiftKitMacros", type: "CustomEquatable")


@attached(member, names: arbitrary)
public macro NodePrinter() = #externalMacro(module: "SageSwiftKitMacros", type: "NodePrinter")
29 changes: 0 additions & 29 deletions Sources/SageSwiftKitClient/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ import SageSwiftKit
import Foundation
import Combine

@AutoMockable(accessLevel: "public")
protocol FooProtocol {
func tmpFunc(var1: @escaping (String?) -> Void, var2: AnyPublisher<Bool, Never>) -> Void
}

let mockProtocol: FooProtocolMock = .init()

struct SubObject: Codable {
let id: Int
let name: String
Expand Down Expand Up @@ -42,25 +35,3 @@ struct PlayingObject {

var identifier: Int
}


@JsonMockable(
keyDecodingStrategy: .convertFromSnakeCase,
bundle: .main
)
struct User: Decodable {
var example: String

static var mockA: Self {
get throws {
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileA")
}
}

static var mockB: Self {
get throws {
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileB")
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ public enum JsonMockable: MemberMacro {
)

declSyntax.append(DeclSyntax(function))




let variable = try VariableDeclSyntax(
"public static var jsonMock: \(name.tokenSyntax)") {
try .init(itemsBuilder: {
Expand Down
103 changes: 103 additions & 0 deletions Sources/SageSwiftKitMacros/MockableMacros/AutoMockableBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// Copyright © 2024 Sage.
// All Rights Reserved.

import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftSyntaxBuilder

public enum AutoMockable: PeerMacro {
static var mocksVarName: String { "mock" }

public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let protocolSyntax = declaration.as(ProtocolDeclSyntax.self) else {
return []
}

let accessLevel = node
.adapter
.findArgument(id: "accessLevel")?
.adapter
.expression(cast: StringLiteralExprSyntax.self)?.representedLiteralValue ?? "internal"

let procotolName = protocolSyntax.name.text

guard let members = declaration.as(ProtocolDeclSyntax.self)?.memberBlock.members else {
return []
}

let variablesToMock: [VariableDeclSyntax] = members.compactMap { $0.decl.as(VariableDeclSyntax.self) }

let functionsToMock: [FunctionsMockData] = members
.compactMap { item -> FunctionsMockData? in
guard let casted = item.decl.as(FunctionDeclSyntax.self) else {
return nil
}

return FunctionsMockData(syntax: casted, accessLevel: accessLevel.tokenSyntax)
}

return [
DeclSyntax(
ClassDeclSyntax(
modifiers: .init(itemsBuilder: {
DeclModifierSyntax(name: accessLevel.tokenSyntax)
}),
name: .identifier("\(procotolName)Mock"),
inheritanceClause: .init(
inheritedTypes: .init(itemsBuilder: {
InheritedTypeSyntax(
type: IdentifierTypeSyntax(
name: .identifier(procotolName)
)
)
})
),
memberBlock: MemberBlockSyntax(
members: try MemberBlockItemListSyntax(itemsBuilder: {
// Classes that has mock data for each function
for funcData in functionsToMock {
ClassMockForFunctionBuilder(funcData: funcData).build()
}

// Class with all functions mock
FunctionMocksClassBuilder(
functions: functionsToMock,
accessLevel: accessLevel.tokenSyntax
).build()

// Variable mocks
FunctionMocksClassBuilder(
functions: functionsToMock,
accessLevel: accessLevel.tokenSyntax
).buildVarForTheClass()

// Implementation of each variable
for variable in variablesToMock {
let varConformance = ProtocolVarsConformanceBuilder(
variable: variable,
accessLevel: accessLevel.tokenSyntax
)

varConformance.build()
varConformance.buildReturnVar()
}

// Implementation of each function
for data in functionsToMock {
try ProtocolFunctionsConformanceBuilder(
data: data
).build()
}
})
)
)
)
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//
// Copyright © 2024 Sage.
// All Rights Reserved.

import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftSyntaxBuilder

// Creates the mock for each function with its parameters calls retun value etc...
struct ClassMockForFunctionBuilder {
let funcData: FunctionsMockData

var parametersName: String { "Parameters" }
var callsName: String { "calls" }

init(funcData: FunctionsMockData) {
self.funcData = funcData
}

func build() -> ClassDeclSyntax {
ClassDeclSyntax(
modifiers: .init(
itemsBuilder: {
.init(name: funcData.accessLevel)
}
),
name: funcData.className.tokenSyntax,
memberBlock: .init(
members: .init(
itemsBuilder: {
if let parametersStruct = buildParameters() {
parametersStruct
}

buildCalls()
buildLastCall()

buildCalled()

if let returnValue = funcData.returnValue {
VariableDeclSyntax(
modifiers: .init(itemsBuilder: {
.init(name: funcData.accessLevel)
}),
Keyword.var,
name: "returnValue",
type: TypeAnnotationSyntax(
type: TypeSyntax(stringLiteral: returnValue)
)
)
}

InitializerDeclSyntax(
signature: .init(parameterClause: .init(
parameters: .init(itemsBuilder: {

})
)),
body: .init(statements: .init(itemsBuilder: {

}))
)
}
)
)
)
}
}

extension ClassMockForFunctionBuilder {
func buildParameters() -> StructDeclSyntax? {
return StructDeclSyntax(
modifiers: .init(itemsBuilder: {
.init(name: funcData.accessLevel)
}),
name: parametersName.tokenSyntax,
memberBlock: .init(members: .init(itemsBuilder: {
for parameter in funcData.params {
VariableDeclSyntax(
modifiers: .init(itemsBuilder: {
.init(name: funcData.accessLevel)
}),
Keyword.let,
name: PatternSyntax(stringLiteral: parameter.name),
type: TypeAnnotationSyntax(
type: TypeSyntax(stringLiteral: parameter.noEscapingType)
)
)
}
}))
)
}

func buildCalls() -> VariableDeclSyntax {
return VariableDeclSyntax(
modifiers: .init(itemsBuilder: {
.init(name: funcData.accessLevel)
}),
Keyword.var,
name: .init(stringLiteral: callsName),
type: TypeAnnotationSyntax(
type: TypeSyntax(stringLiteral: "[\(parametersName)]")
),
initializer: InitializerClauseSyntax(
value: ArrayExprSyntax(elements: .init(itemsBuilder: {

}))
)
)
}

func buildCalled() -> VariableDeclSyntax {
return VariableDeclSyntax(
modifiers: .init(itemsBuilder: {
.init(name: funcData.accessLevel)
}),
bindingSpecifier: .keyword(.var),
bindings: .init(itemsBuilder: {
PatternBindingSyntax(
pattern: IdentifierPatternSyntax(identifier: "called"),
typeAnnotation: TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: "Bool")),
accessorBlock: AccessorBlockSyntax(
leftBrace: .leftBraceToken(),
accessors: .init(CodeBlockItemListSyntax(itemsBuilder: {
ReturnStmtSyntax(
returnKeyword: .keyword(.return),
expression: DeclReferenceExprSyntax(
baseName: "self.lastCall != nil".tokenSyntax
)
)
})),
rightBrace: .rightBraceToken()
)
)
})
)
}

func buildLastCall() -> VariableDeclSyntax {
return VariableDeclSyntax(
modifiers: .init(itemsBuilder: {
.init(name: funcData.accessLevel)
}),
bindingSpecifier: .keyword(.var),
bindings: .init(itemsBuilder: {
PatternBindingSyntax(
pattern: IdentifierPatternSyntax(identifier: "lastCall"),
typeAnnotation: TypeAnnotationSyntax(
type: OptionalTypeSyntax(
wrappedType: IdentifierTypeSyntax(name: parametersName.tokenSyntax)
)
),
accessorBlock: AccessorBlockSyntax(
leftBrace: .leftBraceToken(),
accessors: .init(CodeBlockItemListSyntax(itemsBuilder: {
ReturnStmtSyntax(
returnKeyword: .keyword(.return),
expression: DeclReferenceExprSyntax(
baseName: "self.calls.last".tokenSyntax
)
)
})),
rightBrace: .rightBraceToken()
)
)
})
)
}
}
Loading

0 comments on commit c2633b1

Please sign in to comment.