Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!
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!
240 changes: 215 additions & 25 deletions Sources/IndexStore/IndexStore.swift

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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<SourceSymbol> = []
symbols.forEach {
let subclasses = workspace.occurrences(ofUSR: $0.usr, roles: [.baseOf])
Expand All @@ -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)
}
}
Expand All @@ -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<SourceSymbol> = []
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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,37 @@ 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.
/// - Parameters:
/// - 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<SourceSymbol> = []
symbols.forEach {
let subclasses = workspace.occurrences(ofUSR: $0.usr, roles: [.baseOf])
Expand All @@ -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)
}
}
Expand All @@ -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<SourceSymbol> = []
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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand Down
43 changes: 43 additions & 0 deletions Sources/IndexStore/Public/IndexStoreQuery/IndexStoreQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() {}
Expand Down Expand Up @@ -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
}
}
Loading
Loading