Skip to content

Commit ace3a5f

Browse files
committed
ci: Add Developer Linter
1 parent e2623aa commit ace3a5f

33 files changed

+812
-466
lines changed

.swiftlint.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
disabled_rules: # rules to exclude from running
2+
- trailing_whitespace
3+
4+
opt_in_rules: # some rules are disabled by default, so you need to opt-in
5+
- redundant_void_return
6+
7+
included: # paths to include during linting. Takes precedence over `excluded`.
8+
- Sources
9+
- Tests
10+
- Playground
11+
12+
excluded: # paths to ignore during linting. Takes precedence over `included`.
13+
- Carthage
14+
- Pods
15+
- .build
16+
- Docs
17+
18+
line_length: 120 # max line length

Package.resolved

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ let package = Package(
1717
.library(name: "Astroject-Nexus", targets: ["Nexus"]),
1818
.library(name: "Astroject-Singularity", targets: ["Singularity"])
1919
],
20+
dependencies: [
21+
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.58.2") // Add SwiftLint as a development dependency
22+
],
2023
targets: [
21-
.target(name: "Core"),
22-
.target(name: "Nexus", dependencies: ["Core"]),
23-
.target(name: "Singularity", dependencies: ["Core"]),
24-
24+
.target(name: "Core", plugins: [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")]),
25+
.target(name: "Nexus", dependencies: ["Core"], plugins: [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")]),
26+
.target(name: "Singularity", dependencies: ["Core"], plugins: [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")]),
27+
2528
.testTarget(name: "CoreTests", dependencies: ["Core"]),
2629
.testTarget(name: "NexusTests", dependencies: ["Nexus"]),
2730
.testTarget(name: "SingularityTests", dependencies: ["Singularity"]),

Sources/Core/Assemble/Assembler.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
//
2-
// Assembler.swift
3-
// Astroject
2+
// Assembler.swift
3+
// Astroject
44
//
5-
// Created by Porter McGary on 2/27/25.
5+
// Created by Porter McGary on 2/27/25.
66
//
77

88
import Foundation
99

1010
/// Assembles dependencies into a `Container` using provided `Assembly` instances.
11+
///
12+
/// The `Assembler` class is responsible for applying one or more `Assembly` instances to a `Container`,
13+
/// which registers dependencies and performs any necessary setup.
1114
public class Assembler {
1215
/// The `Container` instance that dependencies are assembled into.
1316
let container: Container
14-
17+
1518
/// A `Resolver` instance that provides access to the assembled dependencies.
19+
/// This property returns the `Container` itself, as it conforms to the `Resolver` protocol.
1620
var resolver: Resolver { container }
1721

1822
/// Initializes an `Assembler` with a given `Container`.
@@ -24,23 +28,35 @@ public class Assembler {
2428

2529
/// Applies a single `Assembly` to the `Container`.
2630
///
31+
/// This function applies the `assemble` and `loaded` methods of the provided `Assembly` instance.
32+
///
2733
/// - Parameter assembly: The `Assembly` instance to apply.
2834
public func apply(assembly: Assembly) {
2935
run(assemblies: [assembly])
3036
}
3137

3238
/// Applies an array of `Assembly` instances to the `Container`.
3339
///
40+
/// This function applies the `assemble` and `loaded` methods of each `Assembly` instance in the provided array.
41+
///
3442
/// - Parameter assemblies: The array of `Assembly` instances to apply.
3543
public func apply(assemblies: [Assembly]) {
3644
run(assemblies: assemblies)
3745
}
3846

3947
/// Runs the assembly process for an array of `Assembly` instances.
4048
///
49+
/// This function iterates through the provided array of `Assembly` instances
50+
/// and calls their `assemble` and `loaded` methods.
51+
/// The `assemble` method registers dependencies in the `Container`,
52+
/// and the `loaded` method performs any post-registration setup.
53+
///
4154
/// - Parameter assemblies: The array of `Assembly` instances to run.
4255
func run(assemblies: [Assembly]) {
56+
// Iterate through the assemblies and call the assemble method on each one to register the dependencies.
4357
assemblies.forEach { $0.assemble(container: container) }
58+
// Iterate through the assemblies again and call the loaded
59+
// method on each one to perform any post-registration configuration.
4460
assemblies.forEach { $0.loaded(resolver: resolver) }
4561
}
4662
}

Sources/Core/Assemble/Assembly.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,42 @@
11
//
2-
// Assembly.swift
3-
// Astroject
2+
// Assembly.swift
3+
// Astroject
44
//
5-
// Created by Porter McGary on 2/27/25.
5+
// Created by Porter McGary on 2/27/25.
66
//
77

88
import Foundation
99

1010
/// A protocol defining an assembly that configures dependencies in a `Container`.
11+
///
12+
/// The `Assembly` protocol is used to define a set of instructions for configuring
13+
/// dependencies within a dependency injection `Container`.
14+
/// Implementations of this protocol are responsible for registering dependencies and performing any necessary setup.
1115
public protocol Assembly {
1216
/// Configures dependencies within the provided `Container`.
1317
///
18+
/// This function is called by an `Assembler` to register dependencies in the given `Container`.
19+
/// Implementations should use the `Container` to register factories and other configuration.
20+
///
1421
/// - Parameter container: The `Container` instance to configure.
1522
func assemble(container: Container)
1623

1724
/// Called after the assembly has been loaded into the `Container`.
1825
///
26+
/// This function is called by an `Assembler` after all assemblies have been processed.
27+
/// It allows performing any post-registration setup or configuration that requires access to
28+
/// the resolved dependencies.
29+
///
1930
/// - Parameter resolver: The `Resolver` instance providing access to the assembled dependencies.
2031
func loaded(resolver: Resolver)
2132
}
2233

2334
public extension Assembly {
2435
/// Default implementation of `loaded(resolver:)`, which does nothing.
2536
///
37+
/// This default implementation is provided for convenience, allowing assemblies
38+
/// that do not require post-registration setup to omit implementing the `loaded` function.
39+
///
2640
/// - Parameter resolver: The `Resolver` instance (unused in the default implementation).
2741
func loaded(resolver: Resolver) {}
2842
}

Sources/Core/AstrojectError.swift

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//
2+
// AstrojectError.swift
3+
// Astroject
4+
//
5+
// Created by Porter McGary on 2/27/25.
6+
//
7+
8+
import Foundation
9+
10+
/// Represents errors that can occur during dependency registration and resolution
11+
/// within the Astroject dependency injection framework.
12+
///
13+
/// This enum consolidates various error scenarios, including issues related to
14+
/// dependency registration, resolution, and factory execution.
15+
/// It conforms to `LocalizedError` to provide user-friendly error descriptions,
16+
/// failure reasons, and recovery suggestions.
17+
public enum AstrojectError: LocalizedError {
18+
/// A registration is attempted with a `RegistrationKey` that already exists.
19+
///
20+
/// This case indicates that a dependency registration with the same type and
21+
/// optional name has already been performed.
22+
case alreadyRegistered(type: String, name: String? = nil)
23+
24+
/// A dependency is requested but no corresponding registration is found in the container.
25+
///
26+
/// This case occurs when attempting to resolve a dependency that has not been registered.
27+
case noRegistrationFound
28+
29+
/// A circular dependency is detected during the resolution process.
30+
///
31+
/// This case indicates that a chain of dependencies forms a loop, preventing successful resolution.
32+
case circularDependencyDetected
33+
34+
/// An error occurred within the factory closure during dependency resolution.
35+
///
36+
/// This case wraps an underlying error that occurred while executing the factory
37+
/// closure responsible for creating a dependency instance.
38+
case underlyingError(Error)
39+
40+
/// Provides a user-friendly description of the error.
41+
public var errorDescription: String? {
42+
switch self {
43+
case .alreadyRegistered:
44+
return "A registration with the same ProductKey already exists."
45+
case .noRegistrationFound:
46+
return "No registration found for the requested dependency."
47+
case .circularDependencyDetected:
48+
return "A circular dependency was detected."
49+
case .underlyingError(let error):
50+
return "An error occurred within the factory closure: \(error.localizedDescription)"
51+
}
52+
}
53+
54+
/// Provides a reason for the error, explaining why it occurred.
55+
public var failureReason: String? {
56+
switch self {
57+
case .alreadyRegistered:
58+
return "Attempting to register a dependency with a ProductKey that has already been used."
59+
case .noRegistrationFound:
60+
return "Register the dependency before attempting to resolve it."
61+
case .circularDependencyDetected:
62+
return "Review your dependency graph to eliminate circular dependencies."
63+
case .underlyingError:
64+
return "Inspect the underlying error for more details."
65+
}
66+
}
67+
68+
/// Provides a suggestion for recovering from the error.
69+
public var recoverySuggestion: String? {
70+
switch self {
71+
case .alreadyRegistered:
72+
return "Use a different ProductKey or remove the existing registration before registering a new one."
73+
case .noRegistrationFound:
74+
return "Use the `register` or `registerAsync` method to register the dependency."
75+
case .circularDependencyDetected:
76+
// swiftlint:disable:next line_length
77+
return "Break the circular dependency by introducing an abstraction or using a different dependency injection pattern or by using `postInitAction` to initialize cyclical dependencies."
78+
case .underlyingError:
79+
return "Check the factory closure for errors and ensure that it's correctly implemented."
80+
}
81+
}
82+
}
83+
84+
/// Extends `AstrojectError` to conform to `Equatable` when the associated `Product` type is also `Equatable`.
85+
///
86+
/// This extension allows for easy comparison of `AstrojectError` instances based on their associated values.
87+
extension AstrojectError: Equatable {
88+
/// Checks if two `AstrojectError` instances are equal.
89+
///
90+
/// - Parameters:
91+
/// - lhs: The left-hand side `AstrojectError` instance.
92+
/// - rhs: The right-hand side `AstrojectError` instance.
93+
/// - Returns: `true` if the errors are equal, `false` otherwise.
94+
public static func == (lhs: AstrojectError, rhs: AstrojectError) -> Bool {
95+
switch (lhs, rhs) {
96+
case (.alreadyRegistered(let lhsType, let lhsName), .alreadyRegistered(let rhsType, let rhsName)):
97+
// Compare the associated types and names for alreadyRegistered errors.
98+
return lhsType == rhsType && lhsName == rhsName
99+
case (.noRegistrationFound, .noRegistrationFound),
100+
(.circularDependencyDetected, .circularDependencyDetected):
101+
// noRegistrationFound and circularDependencyDetected errors are equal if they are the same case.
102+
return true
103+
case (.underlyingError(let lhsError), .underlyingError(let rhsError)):
104+
// Compare the descriptions of the underlying errors.
105+
return String(describing: lhsError) == String(describing: rhsError)
106+
default:
107+
// All other cases are not equal.
108+
return false
109+
}
110+
}
111+
}

Sources/Core/Behavior.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
//
2-
// Behavior.swift
3-
// Astroject
2+
// Behavior.swift
3+
// Astroject
44
//
5-
// Created by Porter McGary on 2/27/25.
5+
// Created by Porter McGary on 2/27/25.
66
//
77

88
import Foundation
99

1010
/// Protocol for adding custom behavior to the `Container` during registration.
11+
///
12+
/// Implement this protocol to add custom logic that is executed after a registration
13+
/// is added to the dependency injection container.
14+
/// This can be useful for logging, analytics, or other side effects related to the registration process.
1115
public protocol Behavior {
1216
/// Called after a registration has been added to the `Container`.
1317
///
1418
/// - Parameters:
1519
/// - type: The type of the product being registered.
1620
/// - container: The `Container` instance to which the registration was added.
17-
/// - registration: The `Registration` instance that was added.
21+
/// - registration: The `Registrable` instance that was added.
1822
/// - name: An optional name associated with the registration.
1923
func didRegister<Product>(
2024
type: Product.Type,

0 commit comments

Comments
 (0)