From 2b4f264fd1add2c619eb5d408e97b7e78296ef34 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 2 Dec 2023 20:03:09 -0800 Subject: [PATCH] Setup plugin structure --- Package.resolved | 36 ++++ Package.swift | 49 +++++- .../SafeDICollectInstantiables.swift | 51 ++++++ .../SafeDIGenerateDependencyTree.swift | 80 +++++++++ .../Generators/DependencyTreeGenerator.swift | 38 +++++ Sources/SafeDICore/Models/Dependency.swift | 3 + Sources/SafeDICore/Models/Instantiable.swift | 10 +- Sources/SafeDICore/Visitors/FileVisitor.swift | 38 ++--- Sources/SafeDIPlugin/SafeDIPlugin.swift | 161 ++++++++++++++++++ Tests/SafeDICoreTests/FileVisitorTests.swift | 8 +- 10 files changed, 441 insertions(+), 33 deletions(-) create mode 100644 Plugins/SafeDICollectInstantiables/SafeDICollectInstantiables.swift create mode 100644 Plugins/SafeDIGenerateDependencyTree/SafeDIGenerateDependencyTree.swift create mode 100644 Sources/SafeDICore/Generators/DependencyTreeGenerator.swift create mode 100644 Sources/SafeDIPlugin/SafeDIPlugin.swift diff --git a/Package.resolved b/Package.resolved index d25b7c1c..9b1200c6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "jjliso8601dateformatter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/michaeleisel/JJLISO8601DateFormatter", + "state" : { + "revision" : "de422afd9a47b72703c30a81423c478337191390", + "version" : "0.1.6" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" + } + }, { "identity" : "swift-macro-testing", "kind" : "remoteSourceControl", @@ -26,6 +44,24 @@ "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", "version" : "509.0.2" } + }, + { + "identity" : "zippyjson", + "kind" : "remoteSourceControl", + "location" : "https://github.com/michaeleisel/ZippyJSON.git", + "state" : { + "revision" : "c4ab804780b64979f19268619dfa563b6be58f7d", + "version" : "1.2.10" + } + }, + { + "identity" : "zippyjsoncfamily", + "kind" : "remoteSourceControl", + "location" : "https://github.com/michaeleisel/ZippyJSONCFamily", + "state" : { + "revision" : "8abdd7a5e943afe68e7b03fdaa63b21c042a3893", + "version" : "1.2.9" + } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index d35f096a..9a2cace1 100644 --- a/Package.swift +++ b/Package.swift @@ -14,17 +14,34 @@ let package = Package( .macCatalyst(.v13) ], products: [ + /// A library containing SafeDI macros, property wrappers, and types. .library( name: "SafeDI", targets: ["SafeDI"] ), + /// A SafeDI plugin that must be run on non-root source modules in a project. + .plugin( + name: "SafeDICollectInstantiables", + targets: ["SafeDICollectInstantiables"] + ), + /// A SafeDI plugin that must be run on the root source module in a project. + .plugin( + name: "SafeDIGenerateDependencyTree", + targets: ["SafeDIGenerateDependencyTree"] + ) ], dependencies: [ .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"), - .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"), + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"), + .package(url: "https://github.com/michaeleisel/ZippyJSON.git", from: "1.2.0"), + .package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.2.0"), ], targets: [ - .target(name: "SafeDI", dependencies: ["SafeDIMacros"]), + // Macros + .target( + name: "SafeDI", + dependencies: ["SafeDIMacros"] + ), .testTarget( name: "SafeDITests", dependencies: [ @@ -52,6 +69,30 @@ let package = Package( .product(name: "MacroTesting", package: "swift-macro-testing"), ] ), + + // Plugins + .plugin( + name: "SafeDICollectInstantiables", + capability: .buildTool(), + dependencies: ["SafeDIPlugin"] + ), + .plugin( + name: "SafeDIGenerateDependencyTree", + capability: .buildTool(), + dependencies: ["SafeDIPlugin"] + ), + .executableTarget( + name: "SafeDIPlugin", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + "ZippyJSON", + "SafeDICore", + ] + ), + + // Core .target( name: "SafeDICore", dependencies: [ @@ -62,9 +103,7 @@ let package = Package( ), .testTarget( name: "SafeDICoreTests", - dependencies: [ - "SafeDICore" - ] + dependencies: ["SafeDICore"] ), ] ) diff --git a/Plugins/SafeDICollectInstantiables/SafeDICollectInstantiables.swift b/Plugins/SafeDICollectInstantiables/SafeDICollectInstantiables.swift new file mode 100644 index 00000000..e511b109 --- /dev/null +++ b/Plugins/SafeDICollectInstantiables/SafeDICollectInstantiables.swift @@ -0,0 +1,51 @@ +import Foundation +import PackagePlugin + +@main +struct SafeDICollectInstantiables: BuildToolPlugin { + func createBuildCommands( + context: PluginContext, + target: Target) + async throws -> [Command] + { + guard let sourceTarget = target as? SourceModuleTarget else { + return [] + } + + let outputSafeDIFile = context.pluginWorkDirectory.appending(subpath: "\(sourceTarget.moduleName).safedi") + let inputSwiftFiles = sourceTarget.sourceFiles(withSuffix: ".swift").map(\.path) + let arguments = inputSwiftFiles + .map(\.string) + .compactMap { $0.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) } + + [ + "--instantiables-output", + outputSafeDIFile + .string + .addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) + ].compactMap { $0 } + + return [ + .buildCommand( + displayName: "SafeDIPlugin", + executable: try context.tool(named: "SafeDIPlugin").path, + arguments: arguments, + environment: [:], + inputFiles: inputSwiftFiles, + outputFiles: [outputSafeDIFile]) + ] + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension SafeDICollectInstantiables: XcodeBuildToolPlugin { + func createBuildCommands( + context: XcodeProjectPlugin.XcodePluginContext, + target: XcodeProjectPlugin.XcodeTarget) + throws -> [PackagePlugin.Command] + { + [] // showstopper TODO: Support Xcode project plugin! + } +} +#endif diff --git a/Plugins/SafeDIGenerateDependencyTree/SafeDIGenerateDependencyTree.swift b/Plugins/SafeDIGenerateDependencyTree/SafeDIGenerateDependencyTree.swift new file mode 100644 index 00000000..7ee51afa --- /dev/null +++ b/Plugins/SafeDIGenerateDependencyTree/SafeDIGenerateDependencyTree.swift @@ -0,0 +1,80 @@ +import Foundation +import PackagePlugin + +@main +struct SafeDIGenerateDependencyTree: BuildToolPlugin { + func createBuildCommands( + context: PluginContext, + target: Target) + async throws -> [Command] + { + guard let sourceTarget = target as? SourceModuleTarget else { + return [] + } + + let inputSwiftFiles = sourceTarget.sourceFiles(withSuffix: ".swift").map(\.path) + let outputSwiftFile = context.pluginWorkDirectory.appending(subpath: "SafeDI.swift") + let targetDependencySafeDIOutputFiles = sourceTarget + .sourceModuleRecursiveDependencies + .map { + context + .pluginWorkDirectory + .removingLastComponent() // Remove `SafeDIGenerateDependencyTree` from path. + .removingLastComponent() // Remove current module name from path. + .appending([ + $0.name, // Dependency module name. + "SafeDICollectInstantiables", // SafeDICollectInstantiables working directory + "\($0.name).safedi" // SafeDICollectInstantiables output file. + ]) + } + .filter { FileManager.default.fileExists(atPath: $0.string) } + + let arguments = inputSwiftFiles + .map(\.string) + .compactMap { $0.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) } + + ["--instantiables-paths"] + + targetDependencySafeDIOutputFiles + .map(\.string) + .compactMap { $0.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) } + + [ + "--dependency-tree-output", + outputSwiftFile + .string + .addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) + ].compactMap { $0 } + + return [ + .buildCommand( + displayName: "SafeDIGenerateDependencyTree", + executable: try context.tool(named: "GenerateDependencyTree").path, + arguments: arguments, + environment: [:], + inputFiles: inputSwiftFiles + targetDependencySafeDIOutputFiles, + outputFiles: [outputSwiftFile]) + ] + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension SafeDIGenerateDependencyTree: XcodeBuildToolPlugin { + func createBuildCommands( + context: XcodeProjectPlugin.XcodePluginContext, + target: XcodeProjectPlugin.XcodeTarget) + throws -> [PackagePlugin.Command] + { + [] // showstopper TODO: Support Xcode project plugin! + } +} +#endif + +extension Target { + + var sourceModuleRecursiveDependencies: [SourceModuleTarget] { + recursiveTargetDependencies.compactMap { + $0 as? SourceModuleTarget + } + } + +} diff --git a/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift b/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift new file mode 100644 index 00000000..4f116aa8 --- /dev/null +++ b/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift @@ -0,0 +1,38 @@ +// Distributed under the MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +public final class DependencyTreeGenerator { + + // MARK: Initialization + + public init(typeDescriptionToFulfillingInstantiable: [TypeDescription : Instantiable]) { + self.typeDescriptionToFulfillingInstantiable = typeDescriptionToFulfillingInstantiable + } + + // MARK: Public + + public func generate() async throws -> String { + "" // TODO: actually generate the type + } + + // MARK: Private + + private let typeDescriptionToFulfillingInstantiable: [TypeDescription : Instantiable] +} diff --git a/Sources/SafeDICore/Models/Dependency.swift b/Sources/SafeDICore/Models/Dependency.swift index f518949f..61a78fba 100644 --- a/Sources/SafeDICore/Models/Dependency.swift +++ b/Sources/SafeDICore/Models/Dependency.swift @@ -23,6 +23,9 @@ import SwiftSyntax /// A representation of a dependency. /// e.g. `@Singleton let mySingleton: MySingleton` public struct Dependency: Codable, Hashable { + + // MARK: Public + public let property: Property public let source: Source diff --git a/Sources/SafeDICore/Models/Instantiable.swift b/Sources/SafeDICore/Models/Instantiable.swift index 7e998b5f..f0ace22e 100644 --- a/Sources/SafeDICore/Models/Instantiable.swift +++ b/Sources/SafeDICore/Models/Instantiable.swift @@ -18,11 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -struct Instantiable: Codable, Hashable { +public struct Instantiable: Codable, Hashable { // MARK: Initialization - init( + public init( instantiableType: TypeDescription, additionalInstantiableTypes: [TypeDescription]?, dependencies: [Dependency]) @@ -34,11 +34,11 @@ struct Instantiable: Codable, Hashable { // MARK: Public /// The types that can be fulfilled with this Instantiable. - let instantiableTypes: [TypeDescription] + public let instantiableTypes: [TypeDescription] /// The concrete type that fulfills `instantiableTypes`. - var concreteInstantiableType: TypeDescription { + public var concreteInstantiableType: TypeDescription { instantiableTypes[0] } /// The ordered dependencies of this Instantiable. - let dependencies: [Dependency] + public let dependencies: [Dependency] } diff --git a/Sources/SafeDICore/Visitors/FileVisitor.swift b/Sources/SafeDICore/Visitors/FileVisitor.swift index a017be31..c76dee36 100644 --- a/Sources/SafeDICore/Visitors/FileVisitor.swift +++ b/Sources/SafeDICore/Visitors/FileVisitor.swift @@ -21,73 +21,73 @@ import SwiftSyntax /// A visitor that can read entire files. A single `FileVisitor` can be used to walk every file in a module. -final class FileVisitor: SyntaxVisitor { +public final class FileVisitor: SyntaxVisitor { // MARK: Initialization - init() { + public init() { super.init(viewMode: .sourceAccurate) } // MARK: SyntaxVisitor - override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { .skipChildren } - override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { .skipChildren } - override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { .skipChildren } - override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { .skipChildren } - override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { .skipChildren } - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { visitDecl(node) } - override func visitPost(_ node: ClassDeclSyntax) { + public override func visitPost(_ node: ClassDeclSyntax) { visitPostDecl(node) } - override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { visitDecl(node) } - override func visitPost(_ node: ActorDeclSyntax) { + public override func visitPost(_ node: ActorDeclSyntax) { visitPostDecl(node) } - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { visitDecl(node) } - override func visitPost(_ node: StructDeclSyntax) { + public override func visitPost(_ node: StructDeclSyntax) { visitPostDecl(node) } - override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { declSyntaxParentCount += 1 return .visitChildren // Make sure there aren't `@Instantiable`s declared within an enum. } - override func visitPost(_ node: EnumDeclSyntax) { + public override func visitPost(_ node: EnumDeclSyntax) { declSyntaxParentCount -= 1 } - // MARK: Internal + // MARK: Public - var instantiables = [Instantiable]() - var disallowedInstantiableDecoratedTypeDescriptions = [TypeDescription]() + public var instantiables = [Instantiable]() + public var nestedInstantiableDecoratedTypeDescriptions = [TypeDescription]() // MARK: Private @@ -99,7 +99,7 @@ final class FileVisitor: SyntaxVisitor { let instantiableVisitor = InstantiableVisitor() instantiableVisitor.walk(node) if let instantiableType = instantiableVisitor.instantiableType { - disallowedInstantiableDecoratedTypeDescriptions.append(instantiableType) + nestedInstantiableDecoratedTypeDescriptions.append(instantiableType) } return .visitChildren } diff --git a/Sources/SafeDIPlugin/SafeDIPlugin.swift b/Sources/SafeDIPlugin/SafeDIPlugin.swift new file mode 100644 index 00000000..cfac53f0 --- /dev/null +++ b/Sources/SafeDIPlugin/SafeDIPlugin.swift @@ -0,0 +1,161 @@ +// Distributed under the MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ArgumentParser +import Foundation +import SafeDICore +import SwiftParser +import ZippyJSON + +@main +struct SafeDIPlugin: AsyncParsableCommand { + + @Argument(help: "The swift files to parse") + var swiftFilePaths: [String] + + @Option(help: "The desired output location of the .safeDI file") + var instantiablesOutput: String? + + @Option(parsing: .upToNextOption, help: "The .safeDI files from dependent targets") + var instantiablesPaths: [String] = [] + + @Option(help: "The desired output location of the swift dependency injection tree") + var dependencyTreeOutput: String? + + func run() async throws { + let module = parsedModule(try await loadSwiftFiles()) + if let firstNestedInstantiableDecoratedTypeDescription = module.nestedInstantiableDecoratedTypeDescriptions.first { + let nestedInstantiable = firstNestedInstantiableDecoratedTypeDescription.asSource + throw CollectInstantiablesError.foundNestedInstantiable(nestedInstantiable) + } + + if let instantiablesOutput { + try writeInstantiables(module.instantiables, toPath: instantiablesOutput) + } + + if let dependencyTreeOutput { + try await DependencyTreeGenerator( + typeDescriptionToFulfillingInstantiable: try await findSafeDIFulfilledTypes() + ) + .generate() + .write(toPath: dependencyTreeOutput) + } + } + + private func loadSwiftFiles() async throws -> [String] { + try await withThrowingTaskGroup( + of: String.self, + returning: [String].self + ) { taskGroup in + for filePath in swiftFilePaths { + taskGroup.addTask { + try String(contentsOfFile: filePath) + } + } + var swiftFiles = [String]() + for try await swiftFile in taskGroup { + swiftFiles.append(swiftFile) + } + return swiftFiles + } + } + + private func parsedModule(_ swiftFileContent: [String]) -> ParsedModule { + let fileVisitor = FileVisitor() + for swiftFileContent in swiftFileContent { + fileVisitor.walk(Parser.parse(source: swiftFileContent)) + } + return ParsedModule( + instantiables: fileVisitor.instantiables, + nestedInstantiableDecoratedTypeDescriptions: fileVisitor.nestedInstantiableDecoratedTypeDescriptions) + } + + private func writeInstantiables(_ instantiables: [Instantiable], toPath path: String) throws { + try JSONEncoder().encode(instantiables).write(toPath: path) + } + + private func findSafeDIFulfilledTypes() async throws -> [TypeDescription: Instantiable] { + try await withThrowingTaskGroup( + of: [Instantiable].self, + returning: [TypeDescription: Instantiable].self + ) { taskGroup in + let decoder = ZippyJSONDecoder() + let instantiablesURLs: [URL] + if #available(macOS 13.0, *) { + instantiablesURLs = instantiablesPaths.map { URL(filePath: $0) } + } else { + instantiablesURLs = instantiablesPaths.map(URL.init(fileURLWithPath:)) + } + + for instantiablesURL in instantiablesURLs { + taskGroup.addTask { + try decoder.decode([Instantiable].self, from: Data(contentsOf: instantiablesURL)) + } + } + var typeDescriptionToFulfillingInstantiable = [TypeDescription: Instantiable]() + for try await moduleInstantiables in taskGroup { + for instantiable in moduleInstantiables { + for instantiableType in instantiable.instantiableTypes { + if typeDescriptionToFulfillingInstantiable[instantiableType] != nil { + throw CollectInstantiablesError.foundDuplicateInstantiable(instantiableType.asSource) + } + typeDescriptionToFulfillingInstantiable[instantiableType] = instantiable + } + } + } + return typeDescriptionToFulfillingInstantiable + } + } + + private struct ParsedModule { + let instantiables: [Instantiable] + let nestedInstantiableDecoratedTypeDescriptions: [TypeDescription] + } + + private enum CollectInstantiablesError: Error, CustomStringConvertible { + case foundNestedInstantiable(String) + case foundDuplicateInstantiable(String) + + var description: String { + switch self { + case let .foundNestedInstantiable(nestedInstantiable): + "@Instantiable types must be top-level declarations. Found nested @Instantiable type: '\(nestedInstantiable)'" + case let .foundDuplicateInstantiable(duplicateInstantiable): + "@Instantiable types must have globally unique names, and fulfill globally unqiue types. Found multiple @Instantiable types fulfilling type named '\(duplicateInstantiable)'" + } + } + } +} + +extension Data { + fileprivate func write(toPath filePath: String) throws { + if #available(macOS 13.0, *) { + try write(to: URL(filePath: filePath)) + } else { + try write(to: URL(fileURLWithPath: filePath)) + } + } +} + +extension String { + fileprivate func write(toPath filePath: String) throws { + try Data(utf8).write(toPath: filePath) + } +} diff --git a/Tests/SafeDICoreTests/FileVisitorTests.swift b/Tests/SafeDICoreTests/FileVisitorTests.swift index c4687793..1d530595 100644 --- a/Tests/SafeDICoreTests/FileVisitorTests.swift +++ b/Tests/SafeDICoreTests/FileVisitorTests.swift @@ -71,7 +71,7 @@ final class FileVisitorTests: XCTestCase { ] ) XCTAssertEqual( - fileVisitor.disallowedInstantiableDecoratedTypeDescriptions, + fileVisitor.nestedInstantiableDecoratedTypeDescriptions, [] ) } @@ -127,7 +127,7 @@ final class FileVisitorTests: XCTestCase { ] ) XCTAssertEqual( - fileVisitor.disallowedInstantiableDecoratedTypeDescriptions, + fileVisitor.nestedInstantiableDecoratedTypeDescriptions, [] ) } @@ -154,7 +154,7 @@ final class FileVisitorTests: XCTestCase { ] ) XCTAssertEqual( - fileVisitor.disallowedInstantiableDecoratedTypeDescriptions, + fileVisitor.nestedInstantiableDecoratedTypeDescriptions, [ .simple(name: "InnerLevel") ] @@ -174,7 +174,7 @@ final class FileVisitorTests: XCTestCase { [] ) XCTAssertEqual( - fileVisitor.disallowedInstantiableDecoratedTypeDescriptions, + fileVisitor.nestedInstantiableDecoratedTypeDescriptions, [ .simple(name: "InnerLevel") ]