Pecker/Sources/PeckerKit/DeadCodeDetecter.swift

93 lines
3.0 KiB
Swift

import Foundation
import Path
import IndexStoreDB
import TSCBasic
public final class DeadCodeDetecter {
public let sourceCodeCollector: SourceCollector
let server: SourceKitServer
let workSpace: Workspace
public init(configuration: Configuration) throws {
sourceCodeCollector = SourceCollector(path: configuration.projectPath)
let buildSystem = DatabaseBuildSystem(indexStorePath: configuration.indexStorePath,
indexDatabasePath: configuration.indexDatabasePath)
workSpace = try Workspace(buildSettings: buildSystem)
server = SourceKitServer(workspace: workSpace)
}
public func detect() throws -> [SourceDetail] {
var deadSources: [SourceDetail] = []
try sourceCodeCollector.collect()
for source in sourceCodeCollector.sources {
if detect(source: source) {
deadSources.append(source)
}
}
return deadSources
}
}
extension DeadCodeDetecter {
/// Detect whether source code if used
/// - Parameter source: The source code to detect
private func detect(source: SourceDetail) -> Bool {
guard let symbol = findSymbol(source: source) else {
return false
}
if symbol.roles.contains(.overrideOf) {
return false
}
let symbolOccurenceResults = server.occurrences(
ofUSR: symbol.symbol.usr,
roles: [.reference],
workspace: workSpace)
if filterExtension(source: source, symbols: symbolOccurenceResults).count > 0 {
return false
} else {
return true
}
}
/// Find source code symbol in the project index
/// - Parameter source: The souece code
private func findSymbol(source: SourceDetail) -> SymbolOccurrence? {
let symbols = server.findWorkspaceSymbols(matching: source.name)
for symbol in symbols {
if isEqual(source: source, symbol: symbol) {
return symbol
}
}
return nil
}
/// In the rule class, struct, enum and protocol extensions are not meant to be used,
/// But in symbol their extensions are defined as refered
/// So we need to fitler their extensions
/// - Parameters:
/// - source: The source code, determine if need filter by source kind.
/// - symbols: All the source symbols
private func filterExtension(source: SourceDetail, symbols: [SymbolOccurrence]) -> [SymbolOccurrence] {
guard source.needFilterExtension else {
return symbols
}
return symbols.lazy.filter { !$0.isSourceExtension(sources: &sourceCodeCollector.sourceExtensions) }
}
}
func isEqual(source: SourceDetail, symbol: SymbolOccurrence) -> Bool {
return source.location.path == symbol.location.path &&
source.location.line == symbol.location.line &&
source.location.column == symbol.location.utf8Column
}