Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docs for @DependencyClient about default values. #164

Merged
merged 5 commits into from
Dec 15, 2023
Merged
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
31 changes: 31 additions & 0 deletions Sources/DependenciesMacros/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,37 @@
/// instances of the client. Creating that initializer manually is quite laborious, and you have to
/// update it each time a new endpoint is added to the client.
///
/// ## Restrictions
///
/// Usage of the ``DependencyClient()`` macro does have a restriction to be aware of. If your
/// client has a closure that is non-throwing and non-void returning like below, then you
/// will get a compile-time error letting you know a default must be provided:
///
/// ```swift
/// @DependencyClient
/// struct APIClient {
/// // 🛑 Default value required for non-throwing closure 'isFavorite'
/// var isFavorite: () -> Bool
/// }
/// ```
///
/// The error also comes with a helpful fix-it to let you know what needs to be done:
///
/// ```swift
/// @DependencyClient
/// struct APIClient {
/// var isFavorite: () -> Bool = { <#Bool#> }
/// }
/// ```
///
/// The reason we require a default for these endpoints is so that you immediately get access to
/// a default client via `APIClient()`, which is handy to use in tests and SwiftUI previews. The
/// only way to do this, without crashing at runtime, is if you provide defaults for your endpoints.
///
/// To fix you must supply a closure that returns a default value. The default value can be anything
/// and does not need to signify a real value. For example, if the endpoint returns a boolean, you
/// can return `false`, or if it returns an array, you can return `[]`.`
///
/// [designing-dependencies]: https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependencies/designingdependencies
/// [separating-interface-implementation]: https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependencies/livepreviewtest#Separating-interface-and-implementation
@attached(member, names: named(init))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public enum DependencyClientMacro: MemberAttributeMacro, MemberMacro {
var unimplementedDefault = functionType.unimplementedDefault
unimplementedDefault.append(placeholder: functionType.returnClause.type.trimmed.description)
context.diagnose(
clientName: declaration.as(StructDeclSyntax.self)?.name.text,
node: binding,
identifier: identifier,
unimplementedDefault: unimplementedDefault
Expand Down
9 changes: 9 additions & 0 deletions Sources/DependenciesMacrosPlugin/Support.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ extension VariableDeclSyntax {

extension MacroExpansionContext {
func diagnose(
clientName: String? = nil,
node: PatternBindingSyntax,
identifier: TokenSyntax,
unimplementedDefault: ClosureExprSyntax
Expand All @@ -188,6 +189,14 @@ extension MacroExpansionContext {
message: MacroExpansionErrorMessage(
"""
Default value required for non-throwing closure '\(identifier)'

Defaults are required so that the macro can generate a default, "unimplemented" version \
of the dependency\(clientName.map { " via '\($0)()'"} ?? ""). The default value can be \
anything and does not need to signify a real value. For example, if the endpoint returns \
a boolean, you can return 'false', or if it returns an array, you can return '[]'.

See the documentation for @DependencyClient for more information: \
https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions
"""
),
fixIt: FixIt(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,14 @@ final class DependencyClientMacroTests: BaseTestCase {
var endpoint: @Sendable () -> Int
┬────────────────────────────
╰─ 🛑 Default value required for non-throwing closure 'endpoint'

Defaults are required so that the macro can generate a default, "unimplemented" version of the dependency via 'Client()'. The default value can be anything and does not need to signify a real value. For example, if the endpoint returns a boolean, you can return 'false', or if it returns an array, you can return '[]'.

See the documentation for @DependencyClient for more information: https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions
✏️ Insert '= { <#Int#> }'
}
"""
} fixes: {
}fixes: {
"""
@DependencyClient
struct Client: Sendable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ final class DependencyEndpointMacroTests: BaseTestCase {
var endpoint: () -> Bool
┬───────────────────
╰─ 🛑 Default value required for non-throwing closure 'endpoint'

Defaults are required so that the macro can generate a default, "unimplemented" version of the dependency. The default value can be anything and does not need to signify a real value. For example, if the endpoint returns a boolean, you can return 'false', or if it returns an array, you can return '[]'.

See the documentation for @DependencyClient for more information: https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions
✏️ Insert '= { <#Bool#> }'
}
"""
} fixes: {
}fixes: {
"""
struct Client {
@DependencyEndpoint
Expand Down Expand Up @@ -142,10 +146,14 @@ final class DependencyEndpointMacroTests: BaseTestCase {
var endpoint: (Int, Bool, String) -> Bool
┬────────────────────────────────────
╰─ 🛑 Default value required for non-throwing closure 'endpoint'

Defaults are required so that the macro can generate a default, "unimplemented" version of the dependency. The default value can be anything and does not need to signify a real value. For example, if the endpoint returns a boolean, you can return 'false', or if it returns an array, you can return '[]'.

See the documentation for @DependencyClient for more information: https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions
✏️ Insert '= { _, _, _ in <#Bool#> }'
}
"""
} fixes: {
}fixes: {
"""
struct Client {
@DependencyEndpoint
Expand Down