diff --git a/README.md b/README.md index 58572d6..4df8a25 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ let colorSymbol indexStore.querySymbols( .first! // Look up any occurrences of the UIColor symbol -let occurrences = indexStore.queryRelatedOccurences(ofSymbol: colorSymbol, query: .empty) +let occurrences = indexStore.queryRelatedOccurrences(ofSymbol: colorSymbol, query: .empty) ``` #### Convenience Methods @@ -295,4 +295,4 @@ git push origin your-feature-branch Please ensure you add unit tests for any changes. The aim is not `100%` coverage, but rather meaningful test coverage that ensures your changes are behaving as expected without negatively effecting existing behaviour. -Please note that the project maintainers may ask you to make changes to your contribution or provide additional information. Be open to feedback and willing to make adjustments as needed. Once your pull request is approved and merged, your changes will become part of the project! \ No newline at end of file +Please note that the project maintainers may ask you to make changes to your contribution or provide additional information. Be open to feedback and willing to make adjustments as needed. Once your pull request is approved and merged, your changes will become part of the project! diff --git a/Sources/IndexStore/IndexStore.swift b/Sources/IndexStore/IndexStore.swift index df22ead..83a3994 100644 --- a/Sources/IndexStore/IndexStore.swift +++ b/Sources/IndexStore/IndexStore.swift @@ -12,6 +12,128 @@ import TSCBasic /// Class abstracting `IndexStoreDB` functionality that serves ``SourceSymbol`` results. public final class IndexStore { + + // MARK: - Supplementary + + /// Enumeration of supported limit strategies to use when resolving inherited symbols. + public enum RecursiveSearchStrategy: Hashable, Sendable { + /// All symbols will be recursively resolved + case all + /// Only symbols immediately referenced to matching results will be resolved + case immediate + /// Symbols will be recursively resolved up until the given recursive limit. + /// - For example, sending `1` would be the same as `.immediate`, sending `2` would be `immediate + 1` and so on. + /// - Note: Sending + case level(Int) + /// Will not attempt to search for any related symbols (parents, inheritance etc) + case noSearching + } + + /// Struct holding the recursive strategies to use when resolving parent and inheritance symbols. + public struct RecursiveSearchConfiguration: Hashable, Sendable { + + /// The strategy to use when resolving parent symbols. + public let parent: RecursiveSearchStrategy + + /// The strategy to use when resolving inheritance symbols. + public let inheritance: RecursiveSearchStrategy + + // MARK: - Lifecycle + + public init(parent: RecursiveSearchStrategy, inheritance: RecursiveSearchStrategy) { + self.parent = parent + self.inheritance = inheritance + } + + + /// Convenience property that returns an instance with the given strategy assigned to the `parent` property and the `inheritance` + /// using the ``IndexStore/RecursiveSearchStrategy/all`` strategy. + /// - Parameter strategy: The strategy to assign. + /// - Returns: ``IndexStore/RecursiveSearchStrategy`` + public static func parent(_ strategy: RecursiveSearchStrategy) -> Self { + .init(parent: strategy, inheritance: .all) + } + + /// Convenience property that returns an instance with the given strategy assigned to the `inheritance` property and the `parent` + /// using the ``IndexStore/RecursiveSearchStrategy/all`` strategy. + /// - Parameter strategy: The strategy to assign. + /// - Returns: ``IndexStore/RecursiveSearchStrategy`` + public static func inheritance(_ strategy: RecursiveSearchStrategy) -> Self { + .init(parent: .all, inheritance: strategy) + } + + /// Convenience property that returns an instance with both parent and inheritance + /// using the ``IndexStore/RecursiveSearchStrategy/all`` strategy. + public static var all: Self { + .init(parent: .all, inheritance: .all) + } + + /// Convenience property that returns an instance with both parent and inheritance + /// using the ``IndexStore/RecursiveSearchStrategy/immediate`` strategy. + public static var immediate: Self { + .init(parent: .immediate, inheritance: .immediate) + } + + /// Convenience property that returns an instance with both parent and inheritance + /// using the ``IndexStore/RecursiveSearchStrategy/level(_:)`` strategy. + public static func level(_ value: Int) -> Self { + let cleanValue = max(0, value) + return .init(parent: .level(cleanValue), inheritance: .level(cleanValue)) + } + + /// Convenience property that returns an instance with both parent and inheritance + /// using the ``IndexStore/RecursiveSearchStrategy/noSearching`` strategy. + public static var noSearching: Self { + .init(parent: .noSearching, inheritance: .noSearching) + } + + /// Will return a new instance using the current ``inheritance`` strategy and assigning the given value to the ``parent`` strategy. + /// - Parameter strategy: The strategy to assign. + /// - Returns: `RecursiveSearchConfiguration` + public func withParentStrategy(_ strategy: RecursiveSearchStrategy) -> Self { + .init(parent: strategy, inheritance: inheritance) + } + + /// Will return a new instance using the current ``parent`` strategy and assigning the given value to the ``inheritance`` strategy. + /// - Parameter strategy: The strategy to assign. + /// - Returns: `RecursiveSearchConfiguration` + public func withInheritanceStrategy(_ strategy: RecursiveSearchStrategy) -> Self { + .init(parent: parent, inheritance: strategy) + } + + // MARK: - Helpers: Internal + + /// Returns a configuration that should be used for the *next* recursive call when resolving a parent. + func nextForParentRecursion() -> Self { + switch parent { + case .all: + return self + case .immediate: + return .init(parent: .noSearching, inheritance: inheritance) + case .level(let level): + let next = level - 1 + return .init(parent: next <= 0 ? .noSearching : .level(next), inheritance: inheritance) + case .noSearching: + return self // should never be used, but keep it stable + } + } + + /// Returns a configuration that should be used for the *next* recursive call when resolving inheritance. + func nextForInheritanceRecursion() -> Self { + switch inheritance { + case .all: + return self + case .immediate: + return .init(parent: parent, inheritance: .noSearching) + case .level(let level): + let next = level - 1 + return .init(parent: parent, inheritance: next <= 0 ? .noSearching : .level(next)) + case .noSearching: + return self + } + } + } + // MARK: - Properties /// The active ``Configuration`` instance any index store derives paths from. @@ -53,7 +175,9 @@ public final class IndexStore { /// For instance, if you have a class named `MyClass` defined in your codebase, querying for this symbol would give you information about `MyClass` itself, such as its location, documentation, /// accessibility level, etc /// - /// - Parameter query: The ``IndexStoreQuery`` to search with. + /// - Parameters: + /// - query: The ``IndexStoreQuery`` to search with. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: `Array` of ``SourceSymbol`` objects. public func querySymbols(_ query: IndexStoreQuery) -> [SourceSymbol] { // Map to workspace expectations @@ -105,7 +229,9 @@ public final class IndexStore { module: query.module ) } - let results = rawResults.compactMap(sourceSymbolFromOccurrence) + let results = rawResults.compactMap { + sourceSymbolFromOccurrence($0, recursiveSearchConfig: query.recursiveSearchConfig) + } return results } @@ -119,8 +245,12 @@ public final class IndexStore { /// - Parameters: /// - symbol: The ``SourceSymbol`` instance to search for. /// - query: The ``IndexStoreQuery`` to search with. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: `Array` of ``SourceSymbol`` objects. - public func queryOccurrences(ofSymbol symbol: SourceSymbol, query: IndexStoreQuery) -> [SourceSymbol] { + public func queryOccurrences( + ofSymbol symbol: SourceSymbol, + query: IndexStoreQuery + ) -> [SourceSymbol] { queryOccurrences(ofUsr: symbol.usr, query: query) } @@ -134,8 +264,12 @@ public final class IndexStore { /// - Parameters: /// - usr: The ``SourceSymbol/usr`` to search with. /// - query: The ``IndexStoreQuery`` to search with. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: `Array` of ``SourceSymbol`` objects. - public func queryOccurrences(ofUsr usr: String, query: IndexStoreQuery) -> [SourceSymbol] { + public func queryOccurrences( + ofUsr usr: String, + query: IndexStoreQuery + ) -> [SourceSymbol] { let symbolKinds = query.kinds.map(\.indexSymbolKind) let symbolRoles = SymbolRole(rawValue: query.roles.rawValue) let targetDirectory = query.restrictToProjectDirectory ? configuration.projectDirectory : nil @@ -151,7 +285,9 @@ public final class IndexStore { restrictToLocation: targetDirectory, module: query.module ) - let results = occurrences.compactMap(sourceSymbolFromOccurrence) + let results = occurrences.compactMap { + sourceSymbolFromOccurrence($0, recursiveSearchConfig: query.recursiveSearchConfig) + } return results } @@ -180,16 +316,30 @@ public final class IndexStore { /// you can query as /// ``` /// let classSymbol = indexStore.querySymbols(.classDeclarations(matching: "CustomClass"))[0] - /// indexStore.queryRelatedOccurences(ofSymbol: classSymbol, query: .withRoles([.definition, .childOf])) // var myInstance: MyClass - /// indexStore.queryRelatedOccurences(ofSymbol: classSymbol, query: .withRoles([.reference, .calledBy, .containedBy])) // myInstance = MyClass() - /// indexStore.queryRelatedOccurences(ofSymbol: classSymbol, query: .withRoles(.all)) // [var myInstance: MyClass, myInstance = MyClass()] + /// indexStore.queryRelatedOccurrences(ofSymbol: classSymbol, query: .withRoles([.definition, .childOf])) // var myInstance: MyClass + /// indexStore.queryRelatedOccurrences(ofSymbol: classSymbol, query: .withRoles([.reference, .calledBy, .containedBy])) // myInstance = MyClass() + /// indexStore.queryRelatedOccurrences(ofSymbol: classSymbol, query: .withRoles(.all)) // [var myInstance: MyClass, myInstance = MyClass()] /// ``` /// - Parameters: /// - symbol: The ``SourceSymbol`` to search with. /// - query: The ``IndexStoreQuery`` to search with. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: `Array` of ``SourceSymbol`` objects. + public func queryRelatedOccurrences( + ofSymbol symbol: SourceSymbol, + query: IndexStoreQuery + ) -> [SourceSymbol] { + queryRelatedOccurrences(ofUsr: symbol.usr, query: query) + } + + @available( + *, + deprecated, + renamed: "queryRelatedOccurrences(ofSymbol:query:)", + message: "Use queryRelatedOccurrences(ofSymbol:query:) to fix the spelling of Occurrences" + ) public func queryRelatedOccurences(ofSymbol symbol: SourceSymbol, query: IndexStoreQuery) -> [SourceSymbol] { - queryRelatedOccurences(ofUsr: symbol.usr, query: query) + queryRelatedOccurrences(ofUsr: symbol.usr, query: query) } /// Will return source symbols that have a defined semantic or structural relation to the given symbol usr value. @@ -216,15 +366,19 @@ public final class IndexStore { /// you can query as /// ``` /// let classSymbol = indexStore.querySymbols(.classDeclarations(matching: "CustomClass"))[0] - /// indexStore.queryRelatedOccurences(ofSymbol: classSymbol, query: .withRoles([.definition, .childOf])) // var myInstance: MyClass - /// indexStore.queryRelatedOccurences(ofSymbol: classSymbol, query: .withRoles([.reference, .calledBy, .containedBy])) // myInstance = MyClass() - /// indexStore.queryRelatedOccurences(ofSymbol: classSymbol, query: .withRoles(.all)) // [var myInstance: MyClass, myInstance = MyClass()] + /// indexStore.queryRelatedOccurrences(ofSymbol: classSymbol, query: .withRoles([.definition, .childOf])) // var myInstance: MyClass + /// indexStore.queryRelatedOccurrences(ofSymbol: classSymbol, query: .withRoles([.reference, .calledBy, .containedBy])) // myInstance = MyClass() + /// indexStore.queryRelatedOccurrences(ofSymbol: classSymbol, query: .withRoles(.all)) // [var myInstance: MyClass, myInstance = MyClass()] /// ``` /// - Parameters: /// - usr: The ``SourceSymbol/usr`` to search with. /// - query: The ``IndexStoreQuery`` to search with. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: `Array` of ``SourceSymbol`` objects. - public func queryRelatedOccurences(ofUsr usr: String, query: IndexStoreQuery) -> [SourceSymbol] { + public func queryRelatedOccurrences( + ofUsr usr: String, + query: IndexStoreQuery + ) -> [SourceSymbol] { let symbolKinds = query.kinds.map(\.indexSymbolKind) let symbolRoles = SymbolRole(rawValue: query.roles.rawValue) let targetDirectory = query.restrictToProjectDirectory ? configuration.projectDirectory : nil @@ -241,10 +395,22 @@ public final class IndexStore { restrictedToSourceFiles: query.sourceFiles ?? [], module: query.module ) - let results = occurrences.compactMap(sourceSymbolFromOccurrence) + let results = occurrences.compactMap { + sourceSymbolFromOccurrence($0, recursiveSearchConfig: query.recursiveSearchConfig) + } return results } + @available( + *, + deprecated, + renamed: "queryRelatedOccurrences(ofUsr:query:)", + message: "Use queryRelatedOccurrences(ofUsr:query:) to fix the spelling of Occurrences" + ) + public func queryRelatedOccurences(ofUsr usr: String, query: IndexStoreQuery) -> [SourceSymbol] { + queryRelatedOccurrences(ofUsr: usr, query: query) + } + /// Will return all swift source file paths within the given project directory. /// - Parameter projectRoot: The project directory to resolve source files from. Default is `Configuration.projectDirectory` /// - Returns: Array of source file path `String` types @@ -332,9 +498,11 @@ public final class IndexStore { /// Transforms the given occurrence into a source symbols instance. /// /// **Note: **Will also look up any inheritance and parents. This can increase time. - /// - Parameter occurrence: The occurrence to transform + /// - Parameters: + /// - occurrence: The occurrence to transform + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: ``SourceSymbol`` - public func sourceSymbolFromOccurrence(_ occurrence: SymbolOccurrence) -> SourceSymbol { + public func sourceSymbolFromOccurrence(_ occurrence: SymbolOccurrence, recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> SourceSymbol { // Resolve source declaration kind let kind = SourceKind(symbolKind: occurrence.symbol.kind) // Source location @@ -342,9 +510,21 @@ public final class IndexStore { // Roles let roles = SourceRole(rawValue: occurrence.roles.rawValue) // Optional parent - let parent = resolveParentForOccurrence(occurrence) + let nextParentConfig = recursiveSearchConfig.nextForParentRecursion() + let parent: SourceSymbol? = { + switch recursiveSearchConfig.parent { + case .noSearching: nil + default: resolveParentForOccurrence(occurrence, recursiveSearchConfig: nextParentConfig) + } + }() // Inheritance - let inheritanceCollection = resolveInheritanceForOccurrence(occurrence) + let nextInheritanceConfig = recursiveSearchConfig.nextForInheritanceRecursion() + let inheritanceCollection: [SourceSymbol] = { + switch recursiveSearchConfig.inheritance { + case .noSearching: [] + default: resolveInheritanceForOccurrence(occurrence, recursiveSearchConfig: nextInheritanceConfig) + } + }() // Result let result = SourceSymbol( name: occurrence.symbol.name, @@ -359,9 +539,14 @@ public final class IndexStore { } /// Will resolve the immediate parent for the given occurrence. - /// - Parameter symbolOccurrence: The `SymbolOccurrence` to resolve for. + /// - Parameters: + /// - symbolOccurrence: The `SymbolOccurrence` to resolve for. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: ``SourceSymbol`` instance or `nil` - public func resolveParentForOccurrence(_ symbolOccurrence: SymbolOccurrence) -> SourceSymbol? { + public func resolveParentForOccurrence( + _ symbolOccurrence: SymbolOccurrence, + recursiveSearchConfig: RecursiveSearchConfiguration = .all + ) -> SourceSymbol? { guard !symbolOccurrence.location.isSystem else { return nil } guard let childOfRelation = symbolOccurrence.relations.first(where: { @@ -379,16 +564,21 @@ public final class IndexStore { else { return nil } - return sourceSymbolFromOccurrence(parentOccurrence) + return sourceSymbolFromOccurrence(parentOccurrence, recursiveSearchConfig: recursiveSearchConfig) } /// Will resolve the source symbols representing the types the given occurrence conforms to or inherits from. - /// - Parameter symbolOccurrence: The `SymbolOccurrence` to resolve for. + /// - Parameters: + /// - symbolOccurrence: The `SymbolOccurrence` to resolve for. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: ``SourceSymbol`` instance or `nil` - public func resolveInheritanceForOccurrence(_ occurrence: SymbolOccurrence) -> [SourceSymbol] { + public func resolveInheritanceForOccurrence( + _ occurrence: SymbolOccurrence, + recursiveSearchConfig: RecursiveSearchConfiguration = .all + ) -> [SourceSymbol] { guard !occurrence.location.isSystem else { return [] } let sourceKind = SourceKind(symbolKind: occurrence.symbol.kind) - let validSourceKinds: [SourceKind] = [.protocol, .struct, .enum, .class, .protocol] + let validSourceKinds: [SourceKind] = [.protocol, .struct, .enum, .class] guard validSourceKinds.contains(sourceKind) else { return [] } logger.debug("resolving inheritance for source `\(occurrence.symbol.name)`") let references = workspace.occurrences(relatedToUSR: occurrence.symbol.usr, roles: [.baseOf]) @@ -411,7 +601,7 @@ public final class IndexStore { targetOccurrence = occurrences.first } guard let result = targetOccurrence else { return } - let details = sourceSymbolFromOccurrence(result) + let details = sourceSymbolFromOccurrence(result, recursiveSearchConfig: recursiveSearchConfig) results.append(details) } return results diff --git a/Sources/IndexStore/Public/Convenience/IndexStore+ClassConvenience.swift b/Sources/IndexStore/Public/Convenience/IndexStore+ClassConvenience.swift index 101e8aa..f41dc1c 100644 --- a/Sources/IndexStore/Public/Convenience/IndexStore+ClassConvenience.swift +++ b/Sources/IndexStore/Public/Convenience/IndexStore+ClassConvenience.swift @@ -12,39 +12,53 @@ extension IndexStore { // MARK: - Convenience: Classes /// Will search for classes that match the given class name, then resolve any source symbols for declarations that subclass the given class. - /// - Parameter className: The name of the class to search for. + /// - Parameters: + /// - className: The name of the class to search for. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: Array of ``SourceSymbol`` - public func sourceSymbols(subclassing className: String) -> [SourceSymbol] { + public func sourceSymbols(subclassing className: String, recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> [SourceSymbol] { // Find classes or declarations (objc class interface) matching the type. let query = IndexStoreQuery.classDeclarations(matching: className) .withIgnoringCase(true) .withRoles([.definition, .declaration]) .withRestrictingToProjectDirectory(false) + .withRecursiveSearchConfig(recursiveSearchConfig) let symbols = querySymbols(query) // Resolve symbols conforming to the matches - return resolveSymbolsSubclassingClassSymbols(symbols) + return resolveSymbolsSubclassingClassSymbols(symbols, recursiveSearchConfig: recursiveSearchConfig) } /// Will search for classes that match the given class name, then resolve any source symbols for declarations that subclass the given class. /// - Parameters: /// - className: The name of the class to search for. /// - sourceFiles: The source files to search in. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: Array of ``SourceSymbol`` - public func sourceSymbols(subclassing className: String, in sourceFiles: [String]) -> [SourceSymbol] { + public func sourceSymbols( + subclassing className: String, + in sourceFiles: [String], + recursiveSearchConfig: RecursiveSearchConfiguration = .all + ) -> [SourceSymbol] { // Resolve symbols for the given class name within the given source files let query = IndexStoreQuery.classDeclarations(matching: className) .withIgnoringCase(true) .withRoles([.definition, .declaration]) .withRestrictingToProjectDirectory(false) + .withRecursiveSearchConfig(recursiveSearchConfig) let symbols = querySymbols(query) // Resolve symbols conforming to the matches within the source files - return resolveSymbolsSubclassingClassSymbols(symbols, in: sourceFiles) + return resolveSymbolsSubclassingClassSymbols(symbols, in: sourceFiles, recursiveSearchConfig: recursiveSearchConfig) } /// Performs the symbol lookups to find symbols subclassing the given class symbols. - /// - Parameter symbols: The symbols to search for. + /// - Parameters: + /// - className: The name of the class to search for. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: Array of ``SourceSymbol`` instances - internal func resolveSymbolsSubclassingClassSymbols(_ symbols: [SourceSymbol]) -> [SourceSymbol] { + internal func resolveSymbolsSubclassingClassSymbols( + _ symbols: [SourceSymbol], + recursiveSearchConfig: RecursiveSearchConfiguration = .all + ) -> [SourceSymbol] { var results: OrderedSet = [] symbols.forEach { let subclasses = workspace.occurrences(ofUSR: $0.usr, roles: [.baseOf]) @@ -68,7 +82,7 @@ extension IndexStore { guard !results.contains(where: { $0.usr == occurrence.symbol.usr }) else { return } - let symbol = sourceSymbolFromOccurrence(occurrence) + let symbol = sourceSymbolFromOccurrence(occurrence, recursiveSearchConfig: recursiveSearchConfig) results.append(symbol) } } @@ -79,18 +93,25 @@ extension IndexStore { /// - Parameters: /// - symbols: The symbols to search for. /// - sourceFiles: The source files to search in. + /// - recursiveSearchConfig: Configuration holding the recursive search strategies to use when searching for parents and inheritance. /// - Returns: Array of ``SourceSymbol`` - internal func resolveSymbolsSubclassingClassSymbols(_ symbols: [SourceSymbol], in sourceFiles: [String]) -> [SourceSymbol] { + internal func resolveSymbolsSubclassingClassSymbols( + _ symbols: [SourceSymbol], + in sourceFiles: [String], + recursiveSearchConfig: RecursiveSearchConfiguration = .all + ) -> [SourceSymbol] { var results: OrderedSet = [] symbols.forEach { symbol in let baseOfRelations = workspace.occurrences(ofUSR: symbol.usr, roles: [.baseOf]).flatMap(\.relations) let declarationSymbols = workspace.symbolsInSourceFiles(at: sourceFiles, roles: [.definition, .declaration]) declarationSymbols.forEach { declaration in guard baseOfRelations.contains(where: { $0.symbol.usr == declaration.symbol.usr }) else { return } - let sourceSymbol = sourceSymbolFromOccurrence(declaration) + let sourceSymbol = sourceSymbolFromOccurrence(declaration, recursiveSearchConfig: recursiveSearchConfig) results.append(sourceSymbol) } } return results.contents } } + +// TODO: Integrate search strategies diff --git a/Sources/IndexStore/Public/Convenience/IndexStore+ExtensionConvenience.swift b/Sources/IndexStore/Public/Convenience/IndexStore+ExtensionConvenience.swift index 4ead276..bd4b03c 100644 --- a/Sources/IndexStore/Public/Convenience/IndexStore+ExtensionConvenience.swift +++ b/Sources/IndexStore/Public/Convenience/IndexStore+ExtensionConvenience.swift @@ -15,7 +15,7 @@ public extension IndexStore { /// Will return any source symbols for any **empty** extensions on types matching the given query. /// /// **Note: ** The provided query will have the `kinds` and `roles` modified to enable the search. - /// - Parameter query: The query to search with. + /// - Parameter query: The name of the class to search for. /// - Returns: Array of ``SourceSymbol`` instances func sourceSymbols(forEmptyExtensionsMatching query: IndexStoreQuery) -> [SourceSymbol] { let symbols = querySymbols(query) @@ -24,7 +24,7 @@ public extension IndexStore { let references = workspace.occurrences(ofUSR: $0.usr, roles: [.reference]) references.forEach { reference in guard reference.roles.contains([.reference]), reference.relations.isEmpty else { return } - var details = sourceSymbolFromOccurrence(reference) + var details = sourceSymbolFromOccurrence(reference, recursiveSearchConfig: query.recursiveSearchConfig) details.sourceKind = .extension results.append(details) } diff --git a/Sources/IndexStore/Public/Convenience/IndexStore+InvocationConvenience.swift b/Sources/IndexStore/Public/Convenience/IndexStore+InvocationConvenience.swift index 34065c7..482a1f1 100644 --- a/Sources/IndexStore/Public/Convenience/IndexStore+InvocationConvenience.swift +++ b/Sources/IndexStore/Public/Convenience/IndexStore+InvocationConvenience.swift @@ -23,7 +23,7 @@ public extension IndexStore { /// - ``SourceKind/classProperty`` /// - Parameter symbol: The symbol occurrence to assess /// - Returns: Array of `SourceSymbol` instances - func invocationsOfSymbol(_ symbol: SourceSymbol) -> [SourceSymbol] { + func invocationsOfSymbol(_ symbol: SourceSymbol, recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> [SourceSymbol] { let validSourceKinds: [SourceKind] = SourceKind.allFunctions + SourceKind.properties guard validSourceKinds.contains(symbol.sourceKind) else { logger.warning("symbol with kind `\(symbol.sourceKind.rawValue) is not valid for this method. Returning empty results.") @@ -32,7 +32,7 @@ public extension IndexStore { var results: [SourceSymbol] = [] let conforming = workspace.occurrences(ofUSR: symbol.usr, roles: [.calledBy, .write, .read]) for symbol in conforming { - let sourceSymbol = sourceSymbolFromOccurrence(symbol) + let sourceSymbol = sourceSymbolFromOccurrence(symbol, recursiveSearchConfig: recursiveSearchConfig) results.append(sourceSymbol) } return results @@ -50,8 +50,8 @@ public extension IndexStore { /// - ``SourceKind/classProperty`` /// - Parameter symbol: The symbol occurrence to assess /// - Returns: `Bool` - func isSymbolInvokedByTestCase(_ symbol: SourceSymbol) -> Bool { - let haystack = invocationsOfSymbol(symbol) + func isSymbolInvokedByTestCase(_ symbol: SourceSymbol, recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> Bool { + let haystack = invocationsOfSymbol(symbol, recursiveSearchConfig: recursiveSearchConfig) var testFunctionFound = false for result in haystack { var parent: SourceSymbol? = result.parent @@ -68,7 +68,8 @@ public extension IndexStore { return false } - /// Will recursively assess the inheritance stack of the given symbol and return `true` when the `name` property of an inherited symbol matches the given term. + /// Will recursively assess the inheritance stack of the given symbol and return `true` when the `name` property + /// of an inherited symbol matches the given term. /// - Parameters: /// - symbol: The symbol to assess. /// - name: The name to match with. diff --git a/Sources/IndexStore/Public/Convenience/IndexStore+ProtocolConvenience.swift b/Sources/IndexStore/Public/Convenience/IndexStore+ProtocolConvenience.swift index 8464bc7..15040b7 100644 --- a/Sources/IndexStore/Public/Convenience/IndexStore+ProtocolConvenience.swift +++ b/Sources/IndexStore/Public/Convenience/IndexStore+ProtocolConvenience.swift @@ -14,14 +14,15 @@ extension IndexStore { /// Will search for protocols that match the given protocol name, then resolve any source symbols for declarations that conform to the given protocol. /// - Parameter protocolName: The name of the protocol to search for. /// - Returns: Array of ``SourceSymbol`` - public func sourceSymbols(conformingToProtocol protocolName: String) -> [SourceSymbol] { + public func sourceSymbols(conformingToProtocol protocolName: String, recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> [SourceSymbol] { // Find any matching protocol symbols let query = IndexStoreQuery.protocolDeclarations(matching: protocolName) .withIgnoringCase(true) .withRestrictingToProjectDirectory(false) + .withRecursiveSearchConfig(recursiveSearchConfig) let symbols = querySymbols(query) // Resolve conforming instances - return resolveSymbolsConformingToProtocolSymbols(symbols) + return resolveSymbolsConformingToProtocolSymbols(symbols, recursiveSearchConfig: recursiveSearchConfig) } /// Will search for protocols that match the given protocol name, then resolve any source symbols for declarations that conform to the given protocol within the given source files. @@ -29,20 +30,21 @@ extension IndexStore { /// - protocolName: The name of the protocol to search for. /// - sourceFiles: The source files to search in. /// - Returns: Array of ``SourceSymbol`` - public func sourceSymbols(conformingToProtocol protocolName: String, in sourceFiles: [String]) -> [SourceSymbol] { + public func sourceSymbols(conformingToProtocol protocolName: String, in sourceFiles: [String], recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> [SourceSymbol] { // Find any matching protocol symbols let query = IndexStoreQuery.protocolDeclarations(matching: protocolName) .withIgnoringCase(true) .withRestrictingToProjectDirectory(false) + .withRecursiveSearchConfig(recursiveSearchConfig) let symbols = querySymbols(query) // Find any matching protocol symbols within the given source files - return resolveSymbolsConformingToProtocolSymbols(symbols, in: sourceFiles) + return resolveSymbolsConformingToProtocolSymbols(symbols, in: sourceFiles, recursiveSearchConfig: recursiveSearchConfig) } /// Performs the symbol lookups to find symbols conforming to protocols in the given array. /// - Parameter symbols: The symbols to search for. /// - Returns: Array of ``SourceSymbol`` instances - internal func resolveSymbolsConformingToProtocolSymbols(_ symbols: [SourceSymbol]) -> [SourceSymbol] { + internal func resolveSymbolsConformingToProtocolSymbols(_ symbols: [SourceSymbol], recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> [SourceSymbol] { var results: OrderedSet = [] symbols.forEach { let subclasses = workspace.occurrences(ofUSR: $0.usr, roles: [.baseOf]) @@ -66,7 +68,7 @@ extension IndexStore { guard !results.contains(where: { $0.usr == occurrence.symbol.usr }) else { return } - let symbol = sourceSymbolFromOccurrence(occurrence) + let symbol = sourceSymbolFromOccurrence(occurrence, recursiveSearchConfig: recursiveSearchConfig) results.append(symbol) } } @@ -78,14 +80,18 @@ extension IndexStore { /// - symbols: The symbols to for. /// - sourceFiles: The source files to search in. /// - Returns: Array of ``SourceSymbol`` instances - internal func resolveSymbolsConformingToProtocolSymbols(_ symbols: [SourceSymbol], in sourceFiles: [String]) -> [SourceSymbol] { + internal func resolveSymbolsConformingToProtocolSymbols( + _ symbols: [SourceSymbol], + in sourceFiles: [String], + recursiveSearchConfig: RecursiveSearchConfiguration = .all + ) -> [SourceSymbol] { var results: OrderedSet = [] symbols.forEach { symbol in let baseOfRelations = workspace.occurrences(ofUSR: symbol.usr, roles: [.baseOf]).flatMap(\.relations) let declarationSymbols = workspace.symbolsInSourceFiles(at: sourceFiles, roles: [.definition, .declaration]) declarationSymbols.forEach { declaration in guard baseOfRelations.contains(where: { $0.symbol.usr == declaration.symbol.usr }) else { return } - let sourceSymbol = sourceSymbolFromOccurrence(declaration) + let sourceSymbol = sourceSymbolFromOccurrence(declaration, recursiveSearchConfig: recursiveSearchConfig) results.append(sourceSymbol) } } diff --git a/Sources/IndexStore/Public/Convenience/IndexStore+References.swift b/Sources/IndexStore/Public/Convenience/IndexStore+References.swift index 65a47ae..156c991 100644 --- a/Sources/IndexStore/Public/Convenience/IndexStore+References.swift +++ b/Sources/IndexStore/Public/Convenience/IndexStore+References.swift @@ -31,13 +31,13 @@ public extension IndexStore { /// ``` /// - Parameter symbol: The symbol to search with. /// - Returns: `Array` of ``SourceSymbol`` objects. - func sourceSymbols(forPropertiesWithTypeOf symbol: SourceSymbol) -> [SourceSymbol] { + func sourceSymbols(forPropertiesWithTypeOf symbol: SourceSymbol, recursiveSearchConfig: RecursiveSearchConfiguration = .all) -> [SourceSymbol] { let symbolKinds = SourceKind.properties.map(\.indexSymbolKind) // Resolve valid occurrences based on roles let rawResults = workspace.occurrences(ofUSR: symbol.usr, roles: [.reference, .containedBy]) - let baseOccurences = rawResults.map(sourceSymbolFromOccurrence) + let baseOccurrences = rawResults.map { sourceSymbolFromOccurrence($0, recursiveSearchConfig: recursiveSearchConfig) } // Grab the property parents of valid occurrences - let validOccurences: [SourceSymbol] = baseOccurences.compactMap { + let validOccurrences: [SourceSymbol] = baseOccurrences.compactMap { guard let parent = $0.parent, workspace.validateKind(parent.sourceKind.indexSymbolKind, kinds: symbolKinds, canIgnore: false) @@ -46,7 +46,7 @@ public extension IndexStore { } return parent } - return validOccurences + return validOccurrences } /// Will return property declaration symbol occurrences of the resolved type of the given symbol within the given source files. diff --git a/Sources/IndexStore/Public/IndexStoreQuery/IndexStoreQuery.swift b/Sources/IndexStore/Public/IndexStoreQuery/IndexStoreQuery.swift index 9b82c47..fcfcd84 100644 --- a/Sources/IndexStore/Public/IndexStoreQuery/IndexStoreQuery.swift +++ b/Sources/IndexStore/Public/IndexStoreQuery/IndexStoreQuery.swift @@ -9,6 +9,7 @@ import IndexStoreDB /// Struct representing a query to give to an `IndexStore` instance to search for source symbols. public struct IndexStoreQuery: Equatable, Sendable { + // MARK: - Properties /// The type or name query to search for. @@ -41,6 +42,30 @@ public struct IndexStoreQuery: Equatable, Sendable { /// Bool whether to perform a case insensitive search. Default is `false`. public var ignoreCase: Bool = false + public var recursiveSearchConfig: IndexStore.RecursiveSearchConfiguration = .all + + /// The strategy to use when resolving parent symbols of results. + /// - Note: Defaults to all + public var parentSearchStrategy: IndexStore.RecursiveSearchStrategy { + get { + recursiveSearchConfig.parent + } + set { + recursiveSearchConfig = recursiveSearchConfig.withParentStrategy(newValue) + } + } + + /// The strategy to use when resolving parent symbols of results. + /// - Note: Defaults to all + public var inheritanceSearchStrategy: IndexStore.RecursiveSearchStrategy { + get { + recursiveSearchConfig.inheritance + } + set { + recursiveSearchConfig = recursiveSearchConfig.withInheritanceStrategy(newValue) + } + } + // MARK: - Lifecycle public init() {} @@ -142,4 +167,22 @@ public struct IndexStoreQuery: Equatable, Sendable { result.ignoreCase = ignoreCase return result } + + public func withRecursiveSearchConfig(_ value: IndexStore.RecursiveSearchConfiguration) -> IndexStoreQuery { + var result = self + result.recursiveSearchConfig = value + return result + } + + public func withParentSearchStrategy(_ value: IndexStore.RecursiveSearchStrategy) -> IndexStoreQuery { + var result = self + result.parentSearchStrategy = value + return result + } + + public func withInheritanceSearchStrategy(_ value: IndexStore.RecursiveSearchStrategy) -> IndexStoreQuery { + var result = self + result.inheritanceSearchStrategy = value + return result + } } diff --git a/Tests/IndexStoreTests/IndexStore/IndexStoreTests.swift b/Tests/IndexStoreTests/IndexStore/IndexStoreTests.swift index c7bb44e..fcf0a9b 100644 --- a/Tests/IndexStoreTests/IndexStore/IndexStoreTests.swift +++ b/Tests/IndexStoreTests/IndexStore/IndexStoreTests.swift @@ -53,7 +53,7 @@ final class IndexStoreTests: XCTestCase { func test_querySymbols_inSourceFiles_classes_willReturnExpectedValues() throws { let query = IndexStoreQuery.classDeclarations(in: sampleSourceFilePaths) let results = instanceUnderTest.querySymbols(query) - XCTAssertEqual(results.count, 21) + XCTAssertEqual(results.count, 22) var result = results[0] XCTAssertNil(result.parent) XCTAssertEqual(result.name, "RootClass") @@ -90,7 +90,7 @@ final class IndexStoreTests: XCTestCase { // MARK: - Tests: Occurrences - func test_queryOccurences_NSColor_willReturnExpectedResuls() throws { + func test_queryOccurrences_NSColor_willReturnExpectedResults() throws { let colorQuery = IndexStoreQuery .withQuery("NSColor") .withAnchorStart(true) @@ -175,9 +175,9 @@ final class IndexStoreTests: XCTestCase { // MARK: - Tests: Relations - func test_queryRelatedOccurences_willReturnExpectedResults() throws { + func test_queryRelatedOccurrences_willReturnExpectedResults() throws { let classSymbol = try XCTUnwrap(instanceUnderTest.querySymbols(.classDeclarations(matching: "CustomClass")).first) - let related = instanceUnderTest.queryRelatedOccurences( + let related = instanceUnderTest.queryRelatedOccurrences( ofSymbol: classSymbol, query: .withRoles([.reference, .call, .calledBy, .containedBy]).withKinds(SourceKind.properties) ).sorted(by: { $0.usr < $1.usr }) @@ -235,10 +235,10 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(symbol.location.offset, 13) } - func test_queryRelatedOccurences_inSourceFiles_willReturnExpectedResults() throws { + func test_queryRelatedOccurrences_inSourceFiles_willReturnExpectedResults() throws { let filePath = instanceUnderTest.swiftSourceFiles().first(where: { $0.contains("RelationsRestricted") })! let classSymbol = try XCTUnwrap(instanceUnderTest.querySymbols(.classDeclarations(matching: "CustomClass")).first) - let related = instanceUnderTest.queryRelatedOccurences( + let related = instanceUnderTest.queryRelatedOccurrences( ofSymbol: classSymbol, query: .withSourceFiles([filePath]).withRoles([.reference, .call, .calledBy, .containedBy]).withKinds(SourceKind.properties) ) @@ -271,9 +271,9 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(symbol.location.offset, 13) } - func test_queryRelatedOccurences_enum_willReturnExpectedResults() throws { + func test_queryRelatedOccurrences_enum_willReturnExpectedResults() throws { let enumSymbol = try XCTUnwrap(instanceUnderTest.querySymbols(.enumDeclarations(matching: "RelatedEnum")).first) - let related = instanceUnderTest.queryRelatedOccurences( + let related = instanceUnderTest.queryRelatedOccurrences( ofSymbol: enumSymbol, query: .withRoles([.reference, .call, .calledBy, .containedBy]).withKinds(SourceKind.properties) ) @@ -305,9 +305,9 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(symbol.location.offset, 21) } - func test_queryRelatedOccurences_Thing_willReturnExpectedResults() throws { + func test_queryRelatedOccurrences_Thing_willReturnExpectedResults() throws { let classSymbol = try XCTUnwrap(instanceUnderTest.querySymbols(.classDeclarations(matching: "Thing")).first) - let related = instanceUnderTest.queryRelatedOccurences( + let related = instanceUnderTest.queryRelatedOccurrences( ofSymbol: classSymbol, query: .withRoles([.reference, .call, .calledBy, .containedBy]).withKinds(SourceKind.properties) ) @@ -339,7 +339,7 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(symbol.location.offset, 18) } - func test_queryRelatedOccurences_NSColor_willReturnExpectedResuls() throws { + func test_queryRelatedOccurrences_NSColor_willReturnExpectedResults() throws { let colorQuery = IndexStoreQuery .withQuery("NSColor") .withAnchorStart(true) @@ -349,7 +349,7 @@ final class IndexStoreTests: XCTestCase { let colorSymbols = instanceUnderTest.querySymbols(colorQuery) let targetSymbol = try XCTUnwrap(colorSymbols.first) - let related = instanceUnderTest.queryRelatedOccurences( + let related = instanceUnderTest.queryRelatedOccurrences( ofSymbol: targetSymbol, query: .withRoles([.read]).withKinds([.constructor]) ) @@ -1513,7 +1513,7 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(targetResult.name, "OtherInheritanceSubclass") XCTAssertEqual(targetResult.sourceKind, .class) XCTAssertTrue(targetResult.location.path.hasSuffix("Classes.swift")) - XCTAssertEqual(targetResult.location.line, 15) + XCTAssertEqual(targetResult.location.line, 18) XCTAssertEqual(targetResult.location.column, 7) XCTAssertEqual(targetResult.location.offset, 7) XCTAssertEqual(targetResult.inheritance.map(\.name), ["InheritanceClass"]) @@ -1527,7 +1527,7 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(targetResult.name, "OtherInheritanceSubclass") XCTAssertEqual(targetResult.sourceKind, .class) XCTAssertTrue(targetResult.location.path.hasSuffix("Classes.swift")) - XCTAssertEqual(targetResult.location.line, 15) + XCTAssertEqual(targetResult.location.line, 18) XCTAssertEqual(targetResult.location.column, 7) XCTAssertEqual(targetResult.location.offset, 7) XCTAssertEqual(targetResult.inheritance.map(\.name), ["InheritanceClass"]) @@ -1588,7 +1588,7 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(targetResult.name, "OtherInheritanceSubclass") XCTAssertEqual(targetResult.sourceKind, .class) XCTAssertTrue(targetResult.location.path.hasSuffix("Classes.swift")) - XCTAssertEqual(targetResult.location.line, 15) + XCTAssertEqual(targetResult.location.line, 18) XCTAssertEqual(targetResult.location.column, 7) XCTAssertEqual(targetResult.location.offset, 7) XCTAssertEqual(targetResult.inheritance.map(\.name), ["InheritanceClass"]) @@ -1724,4 +1724,224 @@ final class IndexStoreTests: XCTestCase { XCTAssertEqual(symbol.location.column, 9) XCTAssertEqual(symbol.location.offset, 9) } + + // MARK: - Tests: Recursive Controls + + func test_querySymbols_structs_Inheritance_noSearching_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.structDeclarations(matching: "InheritanceStruct").withInheritanceSearchStrategy(.noSearching)) + let expectedPathSuffix = pathSuffix("Inheritance.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "InheritanceStruct") + XCTAssertEqual(targetResult.sourceKind, .struct) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 3) + XCTAssertEqual(targetResult.location.column, 8) + XCTAssertEqual(targetResult.location.offset, 8) + XCTAssertEqual(targetResult.inheritance.count, 0) + } + + func test_querySymbols_structs_customInheritance_immediate_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.structDeclarations(matching: "CustomInheritanceStruct").withInheritanceSearchStrategy(.immediate)) + let expectedPathSuffix = pathSuffix("Inheritance.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "CustomInheritanceStruct") + XCTAssertEqual(targetResult.sourceKind, .struct) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 17) + XCTAssertEqual(targetResult.location.column, 8) + XCTAssertEqual(targetResult.location.offset, 8) + XCTAssertEqual(targetResult.inheritance.count, 2) + var nextInheritance = targetResult.inheritance[0] + XCTAssertEqual(nextInheritance.name, "ProtocolWithInheritance") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 9) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertEqual(nextInheritance.inheritance.count, 0) + nextInheritance = targetResult.inheritance[1] + XCTAssertEqual(nextInheritance.name, "RootProtocol") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 3) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertTrue(nextInheritance.inheritance.isEmpty) + } + + func test_querySymbols_structs_customInheritance_all_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.structDeclarations(matching: "CustomInheritanceStruct").withInheritanceSearchStrategy(.all)) + let expectedPathSuffix = pathSuffix("Inheritance.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "CustomInheritanceStruct") + XCTAssertEqual(targetResult.sourceKind, .struct) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 17) + XCTAssertEqual(targetResult.location.column, 8) + XCTAssertEqual(targetResult.location.offset, 8) + XCTAssertEqual(targetResult.inheritance.count, 2) + var nextInheritance = targetResult.inheritance[0] + XCTAssertEqual(nextInheritance.name, "ProtocolWithInheritance") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 9) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertEqual(nextInheritance.inheritance.count, 1) + nextInheritance = targetResult.inheritance[1] + XCTAssertEqual(nextInheritance.name, "RootProtocol") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 3) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertTrue(nextInheritance.inheritance.isEmpty) + } + + func test_querySymbols_structs_customInheritance_level_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.structDeclarations(matching: "CustomInheritanceStruct").withInheritanceSearchStrategy(.level(1))) + let expectedPathSuffix = pathSuffix("Inheritance.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "CustomInheritanceStruct") + XCTAssertEqual(targetResult.sourceKind, .struct) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 17) + XCTAssertEqual(targetResult.location.column, 8) + XCTAssertEqual(targetResult.location.offset, 8) + XCTAssertEqual(targetResult.inheritance.count, 2) + var nextInheritance = targetResult.inheritance[0] + XCTAssertEqual(nextInheritance.name, "ProtocolWithInheritance") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 9) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertEqual(nextInheritance.inheritance.count, 0) + nextInheritance = targetResult.inheritance[1] + XCTAssertEqual(nextInheritance.name, "RootProtocol") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 3) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertTrue(nextInheritance.inheritance.isEmpty) + } + + func test_querySymbols_structs_customInheritance_level_two_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.structDeclarations(matching: "CustomInheritanceStruct").withInheritanceSearchStrategy(.all)) + let expectedPathSuffix = pathSuffix("Inheritance.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "CustomInheritanceStruct") + XCTAssertEqual(targetResult.sourceKind, .struct) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 17) + XCTAssertEqual(targetResult.location.column, 8) + XCTAssertEqual(targetResult.location.offset, 8) + XCTAssertEqual(targetResult.inheritance.count, 2) + var nextInheritance = targetResult.inheritance[0] + XCTAssertEqual(nextInheritance.name, "ProtocolWithInheritance") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 9) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertEqual(nextInheritance.inheritance.count, 1) + nextInheritance = targetResult.inheritance[1] + XCTAssertEqual(nextInheritance.name, "RootProtocol") + XCTAssertEqual(nextInheritance.sourceKind, .protocol) + XCTAssertEqual(nextInheritance.location.line, 3) + XCTAssertEqual(nextInheritance.location.column, 10) + XCTAssertEqual(nextInheritance.location.offset, 10) + XCTAssertTrue(nextInheritance.location.path.hasSuffix("Protocols.swift")) + XCTAssertTrue(nextInheritance.inheritance.isEmpty) + } + + func test_querySymbols_parents_noSearching_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.classDeclarations(matching: "TripleNestedClass").withParentSearchStrategy(.noSearching)) + let expectedPathSuffix = pathSuffix("Classes.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "TripleNestedClass") + XCTAssertEqual(targetResult.sourceKind, .class) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 9) + XCTAssertEqual(targetResult.location.column, 19) + XCTAssertEqual(targetResult.location.offset, 19) + XCTAssertNil(targetResult.parent) + } + + func test_querySymbols_parents_immediate_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.classDeclarations(matching: "TripleNestedClass").withParentSearchStrategy(.immediate)) + let expectedPathSuffix = pathSuffix("Classes.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "TripleNestedClass") + XCTAssertEqual(targetResult.sourceKind, .class) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 9) + XCTAssertEqual(targetResult.location.column, 19) + XCTAssertEqual(targetResult.location.offset, 19) + XCTAssertEqual(targetResult.parent?.name, "DoubleNestedClass") + XCTAssertEqual(targetResult.parent?.sourceKind, .class) + XCTAssertNil(targetResult.parent?.parent) + } + + func test_querySymbols_parents_level_one_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.classDeclarations(matching: "TripleNestedClass").withParentSearchStrategy(.level(1))) + let expectedPathSuffix = pathSuffix("Classes.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "TripleNestedClass") + XCTAssertEqual(targetResult.sourceKind, .class) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 9) + XCTAssertEqual(targetResult.location.column, 19) + XCTAssertEqual(targetResult.location.offset, 19) + XCTAssertEqual(targetResult.parent?.name, "DoubleNestedClass") + XCTAssertEqual(targetResult.parent?.sourceKind, .class) + XCTAssertNil(targetResult.parent?.parent) + } + + func test_querySymbols_parents_level_two_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.classDeclarations(matching: "TripleNestedClass").withParentSearchStrategy(.level(2))) + let expectedPathSuffix = pathSuffix("Classes.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "TripleNestedClass") + XCTAssertEqual(targetResult.sourceKind, .class) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 9) + XCTAssertEqual(targetResult.location.column, 19) + XCTAssertEqual(targetResult.location.offset, 19) + XCTAssertEqual(targetResult.parent?.name, "DoubleNestedClass") + XCTAssertEqual(targetResult.parent?.sourceKind, .class) + XCTAssertEqual(targetResult.parent?.parent?.name, "NestedClass") + XCTAssertEqual(targetResult.parent?.parent?.sourceKind, .class) + XCTAssertNil(targetResult.parent?.parent?.parent) + } + + func test_querySymbols_parents_all_willReturnExpectedValues() throws { + let results = instanceUnderTest.querySymbols(.classDeclarations(matching: "TripleNestedClass").withParentSearchStrategy(.all)) + let expectedPathSuffix = pathSuffix("Classes.swift") + XCTAssertEqual(results.count, 1) + let targetResult = results[0] + XCTAssertEqual(targetResult.name, "TripleNestedClass") + XCTAssertEqual(targetResult.sourceKind, .class) + XCTAssertTrue(targetResult.location.path.hasSuffix(expectedPathSuffix)) + XCTAssertEqual(targetResult.location.line, 9) + XCTAssertEqual(targetResult.location.column, 19) + XCTAssertEqual(targetResult.location.offset, 19) + XCTAssertEqual(targetResult.parent?.name, "DoubleNestedClass") + XCTAssertEqual(targetResult.parent?.sourceKind, .class) + XCTAssertEqual(targetResult.parent?.parent?.name, "NestedClass") + XCTAssertEqual(targetResult.parent?.parent?.sourceKind, .class) + XCTAssertEqual(targetResult.parent?.parent?.parent?.name, "RootClass") + XCTAssertEqual(targetResult.parent?.parent?.parent?.sourceKind, .class) + XCTAssertNil(targetResult.parent?.parent?.parent?.parent) + } } diff --git a/Tests/IndexStoreTests/Samples/Classes.swift b/Tests/IndexStoreTests/Samples/Classes.swift index 9021f60..bb56554 100644 --- a/Tests/IndexStoreTests/Samples/Classes.swift +++ b/Tests/IndexStoreTests/Samples/Classes.swift @@ -6,6 +6,9 @@ class RootClass { class DoubleNestedClass { + class TripleNestedClass { + + } } } }