Skip to content

Commit

Permalink
Add baseline capability (#751)
Browse files Browse the repository at this point in the history
  • Loading branch information
ileitch authored Jun 9, 2024
1 parent 340942c commit e676e1a
Show file tree
Hide file tree
Showing 18 changed files with 104 additions and 13 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

##### Enhancements

- None.
- Added baseline support. Write a baseline with `--write-baseline <file>` and use it with `--baseline <file>`.

##### Bug Fixes

Expand Down
19 changes: 18 additions & 1 deletion Sources/Frontend/Commands/ScanBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,30 @@ final class ScanBehavior {

do {
results = try block(project)

let interval = logger.beginInterval("result:output")
let filteredResults = OutputDeclarationFilter().filter(results)
var baseline: Baseline?

if let baselinePath = configuration.baseline {
let data = try Data(contentsOf: baselinePath.url)
baseline = try JSONDecoder().decode(Baseline.self, from: data)
}

let filteredResults = try OutputDeclarationFilter().filter(results, with: baseline)

if configuration.autoRemove {
try ScanResultRemover().remove(results: filteredResults)
}

if let baselinePath = configuration.writeBaseline {
let usrs = filteredResults
.flatMapSet { $0.usrs }
.union(baseline?.usrs ?? [])
let baseline = Baseline.v1(usrs: usrs.sorted())
let data = try JSONEncoder().encode(baseline)
try data.write(to: baselinePath.url)
}

let output = try configuration.outputFormat.formatter.init(configuration: configuration).format(filteredResults)

if configuration.outputFormat.supportsAuxiliaryOutput {
Expand Down
8 changes: 8 additions & 0 deletions Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ struct ScanCommand: FrontendCommand {
@Option(help: "JSON package manifest path (obtained using `swift package describe --type json` or manually)")
var jsonPackageManifestPath: String?

@Option(help: "Baseline file path used to filter results")
var baseline: FilePath?

@Option(help: "Baseline file path where results are written. Pass the same path to '--baseline' in subsequent scans to exclude the results recorded in the baseline.")
var writeBaseline: FilePath?

private static let defaultConfiguration = Configuration()

func run() throws {
Expand Down Expand Up @@ -174,6 +180,8 @@ struct ScanCommand: FrontendCommand {
configuration.apply(\.$retainCodableProperties, retainCodableProperties)
configuration.apply(\.$retainEncodableProperties, retainEncodableProperties)
configuration.apply(\.$jsonPackageManifestPath, jsonPackageManifestPath)
configuration.apply(\.$baseline, baseline)
configuration.apply(\.$writeBaseline, writeBaseline)

try scanBehavior.main { project in
try Scan().perform(project: project)
Expand Down
8 changes: 8 additions & 0 deletions Sources/PeripheryKit/Indexer/SourceLocation.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import SystemPackage

class SourceLocation {
let file: SourceFile
Expand All @@ -14,6 +15,13 @@ class SourceLocation {
self.hashValueCache = [file.hashValue, line, column].hashValue
}

func relativeTo(_ path: FilePath) -> SourceLocation {
let newPath = file.path.relativeTo(path)
let newFile = SourceFile(path: newPath, modules: file.modules)
newFile.importStatements = file.importStatements
return SourceLocation(file: newFile, line: line, column: column)
}

// MARK: - Private

private func buildDescription(path: String) -> String {
Expand Down
3 changes: 1 addition & 2 deletions Sources/PeripheryKit/Indexer/SwiftIndexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,7 @@ public final class SwiftIndexer: Indexer {

graph.withLock {
for param in params {
let paramDecl = param.declaration
paramDecl.parent = functionDecl
let paramDecl = param.makeDeclaration(withParent: functionDecl)
functionDecl.unusedParameters.insert(paramDecl)
graph.addUnsafe(paramDecl)

Expand Down
13 changes: 13 additions & 0 deletions Sources/PeripheryKit/Results/Baseline.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

/// A baseline set of declarations that are excluded from results.
public enum Baseline: Codable {
case v1(usrs: [String])

public var usrs: Set<String> {
switch self {
case .v1(let usrs):
return Set(usrs)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,52 @@ import FilenameMatcher

public final class OutputDeclarationFilter {
private let configuration: Configuration
private let logger: ContextualLogger
private let logger: Logger
private let contextualLogger: ContextualLogger

public required init(configuration: Configuration = .shared, logger: Logger = .init()) {
self.configuration = configuration
self.logger = logger.contextualized(with: "report:filter")
self.logger = logger
self.contextualLogger = logger.contextualized(with: "report:filter")
}

public func filter(_ declarations: [ScanResult]) -> [ScanResult] {
public func filter(_ declarations: [ScanResult], with baseline: Baseline?) throws -> [ScanResult] {
var declarations = declarations

if let baseline {
var didFilterDeclaration = false
declarations = declarations.filter {
let isDisjoint = $0.usrs.isDisjoint(with: baseline.usrs)
if !isDisjoint {
didFilterDeclaration = true
}
return isDisjoint
}

if !didFilterDeclaration {
logger.warn("No results were filtered by the baseline.")
}
}

if configuration.reportInclude.isEmpty && configuration.reportExclude.isEmpty {
return declarations.sorted { $0.declaration < $1.declaration }
}

return declarations
.filter {
.filter { [contextualLogger] in
let path = $0.declaration.location.file.path

if configuration.reportIncludeMatchers.isEmpty {
if configuration.reportExcludeMatchers.anyMatch(filename: path.string) {
self.logger.debug("Excluding \(path.string)")
contextualLogger.debug("Excluding \(path.string)")
return false
}

return true
}

if configuration.reportIncludeMatchers.anyMatch(filename: path.string) {
self.logger.debug("Including \(path.string)")
contextualLogger.debug("Including \(path.string)")
return true
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/PeripheryKit/ScanResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ public struct ScanResult {

let declaration: Declaration
let annotation: Annotation

public var usrs: Set<String> {
declaration.usrs
}
}
3 changes: 2 additions & 1 deletion Sources/PeripheryKit/SourceGraph/SourceGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ public final class SourceGraph {

func markUnusedModuleImport(_ statement: ImportStatement) {
withLock {
let usr = "\(statement.location.description)-\(statement.module)"
let location = statement.location.relativeTo(.current)
let usr = "import-\(statement.module)-\(location)"
let decl = Declaration(kind: .module, usrs: [usr], location: statement.location)
decl.name = statement.module
unusedModuleImports.insert(decl)
Expand Down
6 changes: 4 additions & 2 deletions Sources/PeripheryKit/Syntax/UnusedParameterParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ final class Parameter: Item, Hashable {
return secondName ?? firstName ?? ""
}

var declaration: Declaration {
func makeDeclaration(withParent parent: Declaration) -> Declaration {
let functionName = function?.fullName ?? "func()"
let usr = "\(functionName)-\(name)-\(location)"
let parentUsrs = parent.usrs.joined(separator: "-")
let usr = "param-\(name)-\(functionName)-\(parentUsrs)"
let decl = Declaration(kind: .varParameter, usrs: [usr], location: location)
decl.name = name
decl.parent = parent
return decl
}

Expand Down
20 changes: 20 additions & 0 deletions Sources/Shared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ public final class Configuration {
@Setting(key: "json_package_manifest_path", defaultValue: nil)
public var jsonPackageManifestPath: String?

@Setting(key: "baseline", defaultValue: nil)
public var baseline: FilePath?

@Setting(key: "write_baseline", defaultValue: nil)
public var writeBaseline: FilePath?

// Non user facing.
public var guidedSetup: Bool = false
public var removalOutputBasePath: FilePath?
Expand Down Expand Up @@ -280,6 +286,14 @@ public final class Configuration {
config[$jsonPackageManifestPath.key] = jsonPackageManifestPath
}

if $baseline.hasNonDefaultValue {
config[$baseline.key] = baseline
}

if $writeBaseline.hasNonDefaultValue {
config[$writeBaseline.key] = writeBaseline
}

return try Yams.dump(object: config)
}

Expand Down Expand Up @@ -373,6 +387,10 @@ public final class Configuration {
$retainEncodableProperties.assign(value)
case $jsonPackageManifestPath.key:
$jsonPackageManifestPath.assign(value)
case $baseline.key:
$baseline.assign(value)
case $writeBaseline.key:
$writeBaseline.assign(value)
default:
logger.warn("\(path.string): invalid key '\(key)'")
}
Expand Down Expand Up @@ -417,6 +435,8 @@ public final class Configuration {
$retainCodableProperties.reset()
$retainEncodableProperties.reset()
$jsonPackageManifestPath.reset()
$baseline.reset()
$writeBaseline.reset()
}

// MARK: - Helpers
Expand Down

0 comments on commit e676e1a

Please sign in to comment.