Skip to content

Commit

Permalink
Keep track of misconfigured Instantiables in the SafeDITool to avoid …
Browse files Browse the repository at this point in the history
…short-circuiting Macro-created build errors
  • Loading branch information
dfed committed Dec 12, 2023
1 parent 19f5b2f commit 42145f6
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 7 deletions.
5 changes: 3 additions & 2 deletions Sources/SafeDICore/Models/Instantiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public struct Instantiable: Codable, Hashable {

public init(
instantiableType: TypeDescription,
initializer: Initializer,
initializer: Initializer?,
additionalInstantiableTypes: [TypeDescription]?,
dependencies: [Dependency],
isClass: Bool)
Expand All @@ -44,7 +44,8 @@ public struct Instantiable: Codable, Hashable {
instantiableTypes[0]
}
/// A memberwise initializer for the concrete instantiable type.
public let initializer: Initializer
/// If `nil`, the Instanitable type is incorrectly configured.
public let initializer: Initializer?
/// The ordered dependencies of this Instantiable.
public let dependencies: [Dependency]
/// Whether the concrete instantiable type is a class.
Expand Down
4 changes: 2 additions & 2 deletions Sources/SafeDICore/Models/ScopeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ actor ScopeGenerator {
} else {
let generateCodeTask = Task {
let argumentList = try instantiable
.initializer
.initializer?
.createInitializerArgumentList(
given: instantiable.dependencies
)
) ?? "/* @Instantiable type is incorrectly configured. Fix errors from @Instantiable macro to fix this error. */"

if let property {
let concreteTypeName = instantiable.concreteInstantiableType.asSource
Expand Down
5 changes: 2 additions & 3 deletions Sources/SafeDICore/Visitors/InstantiableVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,11 @@ public final class InstantiableVisitor: SyntaxVisitor {
var instantiable: Instantiable? {
guard
let instantiableType,
let topLevelDeclarationType,
let initializer = initializers.first(where: { $0.isValid(forFulfilling: dependencies) })
let topLevelDeclarationType
else { return nil }
return Instantiable(
instantiableType: instantiableType,
initializer: initializer,
initializer: initializers.first(where: { $0.isValid(forFulfilling: dependencies) }),
additionalInstantiableTypes: additionalInstantiableTypes,
dependencies: dependencies,
isClass: topLevelDeclarationType.isClass)
Expand Down
169 changes: 169 additions & 0 deletions Tests/SafeDIToolTests/SafeDIToolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,175 @@ final class SafeDIToolTests: XCTestCase {
)
}

func test_run_writesConvenienceExtensionOnRootOfTree_whenRootInstantiatesPropertyWithMissingInstantiableInitializer() async throws {
let output = try await SafeDITool.run(
swiftFileContent: [
"""
@Instantiable()
public final class Root {
public init(child: Child) {
self.child = child
}
@Instantiated
let child: Child
}
""",
"""
@Instantiable()
public final class Child {
// This Child is incorrectly configured! It is missing the required initializer.
@Instantiated
let grandchild: Grandchild
}
""",
"""
@Instantiable()
public final class Grandchild {
public init() {}
}
""",
],
dependentImportStatements: [],
dependentInstantiables: [],
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.
extension Root {
convenience init() {
let child = {
let grandchild = Grandchild()
return Child(/* @Instantiable type is incorrectly configured. Fix errors from @Instantiable macro to fix this error. */)
}()
self.init(child: child)
}
}
"""
)
}

func test_run_writesConvenienceExtensionOnRootOfTree_whenRootInstantiatesPropertyWithNotPublicInstantiableInitializer() async throws {
let output = try await SafeDITool.run(
swiftFileContent: [
"""
@Instantiable()
public final class Root {
public init(child: Child) {
self.child = child
}
@Instantiated
let child: Child
}
""",
"""
@Instantiable()
final class Child {
public init(grandchild: Grandchild) {
self.grandchild = grandchild
}
@Instantiated
let grandchild: Grandchild
}
""",
"""
@Instantiable()
public final class Grandchild {
public init() {}
}
""",
],
dependentImportStatements: [],
dependentInstantiables: [],
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.
extension Root {
convenience init() {
let child = {
let grandchild = Grandchild()
return Child(grandchild: grandchild)
}()
self.init(child: child)
}
}
"""
)
}

func test_run_writesConvenienceExtensionOnRootOfTree_whenRootInstantiatesNonPublicProperty() async throws {
let output = try await SafeDITool.run(
swiftFileContent: [
"""
@Instantiable()
public final class Root {
public init(child: Child) {
self.child = child
}
@Instantiated
let child: Child
}
""",
"""
@Instantiable()
final class Child {
public init(grandchild: Grandchild) {
self.grandchild = grandchild
}
@Instantiated
let grandchild: Grandchild
}
""",
"""
@Instantiable()
public final class Grandchild {
public init() {}
}
""",
],
dependentImportStatements: [],
dependentInstantiables: [],
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.
extension Root {
convenience init() {
let child = {
let grandchild = Grandchild()
return Child(grandchild: grandchild)
}()
self.init(child: child)
}
}
"""
)
}

func test_run_writesConvenienceExtensionOnRootOfTree_whenRootInstantiatesPropertiesWithMultipleTreesThatReceiveTheSameProperty() async throws {
let output = try await SafeDITool.run(
swiftFileContent: [
Expand Down

0 comments on commit 42145f6

Please sign in to comment.