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

Enable aliased received properties to be received again further down the chain #23

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 8 additions & 3 deletions Sources/SafeDICore/Models/Scope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ final class Scope {
.compactMap {
switch $0.source {
case .received:
return $0.fulfillingProperty ?? $0.property
$0.fulfillingProperty ?? $0.property
case .forwarded,
.instantiated:
return nil
nil
Comment on lines -58 to +61
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a style change. not related to the underlying fix here

}
}
}
Expand All @@ -81,7 +81,12 @@ final class Scope {
instantiableStack
.flatMap(\.dependencies)
.filter {
$0.source != .received
(
// If the source is not received, the property has been made available.
$0.source != .received
// If the dependency has a fulfilling property, the property has been aliased.
|| $0.fulfillingProperty != nil
Comment on lines +87 to +88
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches similar code that we have over in the ScopeGenerator:

// If the source is not received, the property is being made available.
$0.source != .received
// If the dependency has a fulfilling property, the property is being aliased.
|| $0.fulfillingProperty != nil

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest the more I think about this (and the PR built on top of this one)... the more I think the abstraction here is wrong. This change works and is mergeable, however I may take a bigger pass at cleaning this up before the next PR makes it out of draft.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah alright pushed up something way better in #24.

)
&& !propertyStack.contains($0.property)
&& $0.property != property
}
Expand Down
151 changes: 151 additions & 0 deletions Tests/SafeDIToolTests/SafeDIToolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2281,6 +2281,157 @@ final class SafeDIToolTests: XCTestCase {
)
}

func test_run_writesConvenienceExtensionOnRootOfTree_whenReceivedPropertyIsRenamedALevelAboveWhereItIsReceived() async throws {
let output = try await executeSystemUnderTest(
swiftFileContent: [
"""
public struct User {}
""",
"""
public protocol UserVendor {
var user: User { get }
}

public protocol UserManager: UserVendor {
var user: User { get set }
}

public final class DefaultUserManager: UserManager {
public init(user: User) {
self.user = user
}

public var user: User
}
""",
"""
public protocol NetworkService {}

@Instantiable(fulfillingAdditionalTypes: [NetworkService.self])
public final class DefaultNetworkService: NetworkService {}
""",
"""
public protocol AuthService {
func login(username: String, password: String) async -> User
}

@Instantiable(fulfillingAdditionalTypes: [AuthService.self])
public final class DefaultAuthService: AuthService {
public func login(username: String, password: String) async -> User {
User(username: username)
}

@Received
let networkService: NetworkService
}
""",
"""
import UIKit

@Instantiable
public final class RootViewController: UIViewController {
public init(authService: AuthService, networkService: NetworkService, loggedInViewControllerBuilder: ForwardingInstantiator<User, LoggedInViewController>) {
self.authService = authService
self.networkService = networkService
self.loggedInViewControllerBuilder = loggedInViewControllerBuilder
super.init(nibName: nil, bundle: nil)
}

@Instantiated
let authService: AuthService

@Instantiated
let networkService: NetworkService

@Instantiated
let loggedInViewControllerBuilder: ForwardingInstantiator<UserManager, LoggedInViewController>

func login(username: String, password: String) {
Task { @MainActor in
let user = await authService.login(username: username, password: password)
let loggedInViewController = loggedInViewControllerBuilder.instantiate(UserManager(user: user))
pushViewController(loggedInViewController)
}
}
}
""",
"""
import UIKit

@Instantiable
public final class LoggedInViewController: UIViewController {
@Forwarded
private let userManager: UserManager

@Received(fulfilledByDependencyNamed: "networkService", ofType: NetworkService.self)
private let userNetworkService: NetworkService

@Instantiated
private let profileViewControllerBuilder: Instantiator<ProfileViewController>
}
""",
"""
import UIKit

@Instantiable
public final class ProfileViewController: UIViewController {
@Received(fulfilledByDependencyNamed: "userManager", ofType: UserManager.self)
private let userVendor: UserVendor

@Instantiated
private let editProfileViewControllerBuilder: Instantiator<EditProfileViewController>
}
""",
"""
import UIKit

@Instantiable
public final class EditProfileViewController: UIViewController {
@Received
private let userVendor: UserVendor
@Received
private let userManager: UserManager
@Received
private let userNetworkService: NetworkService
}
""",
],
buildDependencyTreeOutput: true
)

XCTAssertEqual(
try XCTUnwrap(output.dependencyTree),
"""
// This file was generated by the SafeDIGenerateDependencyTree build tool plugin.
// Any modifications made to this file will be overwritten on subsequent builds.
// Please refrain from editing this file directly.

#if canImport(UIKit)
import UIKit
#endif

extension RootViewController {
public convenience init() {
let networkService: NetworkService = DefaultNetworkService()
let authService: AuthService = DefaultAuthService(networkService: networkService)
let loggedInViewControllerBuilder = ForwardingInstantiator<UserManager, LoggedInViewController> { userManager in
let userNetworkService: NetworkService = networkService
let profileViewControllerBuilder = Instantiator<ProfileViewController> {
let userVendor: UserVendor = userManager
let editProfileViewControllerBuilder = Instantiator<EditProfileViewController> {
EditProfileViewController(userVendor: userVendor, userManager: userManager, userNetworkService: userNetworkService)
}
return ProfileViewController(userVendor: userVendor, editProfileViewControllerBuilder: editProfileViewControllerBuilder)
}
return LoggedInViewController(userManager: userManager, userNetworkService: userNetworkService, profileViewControllerBuilder: profileViewControllerBuilder)
}
self.init(authService: authService, networkService: networkService, loggedInViewControllerBuilder: loggedInViewControllerBuilder)
}
}
"""
)
}

// MARK: Error Tests

func test_run_onCodeWithPropertyWithUnknownFulfilledType_throwsError() async {
Expand Down