From df4568b6697ef2831c9a2255dfc9a62365501f10 Mon Sep 17 00:00:00 2001 From: Ian Leitch Date: Wed, 22 Nov 2023 15:21:45 +0100 Subject: [PATCH] Add option to output relative paths. Closes #638 --- CHANGELOG.md | 2 +- Sources/Frontend/Commands/ScanBehavior.swift | 2 +- Sources/Frontend/Commands/ScanCommand.swift | 4 ++++ .../Formatters/CheckstyleFormatter.swift | 10 +++++++++- .../Formatters/CodeClimateFormatter.swift | 13 ++++++++++--- .../PeripheryKit/Formatters/CsvFormatter.swift | 11 ++++++++++- .../Formatters/JsonFormatter.swift | 12 ++++++++++-- .../Formatters/OutputFormatter.swift | 15 ++++++++++++++- .../Formatters/XcodeFormatter.swift | 18 +++++++++++++----- Sources/Shared/Configuration.swift | 10 ++++++++++ 10 files changed, 82 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bc7b6a3..405ab9f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ##### Enhancements -- None. +- Added the `--relative-results` option to output result paths relative to the current directory. ##### Bug Fixes diff --git a/Sources/Frontend/Commands/ScanBehavior.swift b/Sources/Frontend/Commands/ScanBehavior.swift index 1d102f48d..87ec7648e 100644 --- a/Sources/Frontend/Commands/ScanBehavior.swift +++ b/Sources/Frontend/Commands/ScanBehavior.swift @@ -63,7 +63,7 @@ final class ScanBehavior { results = try block(project) let interval = logger.beginInterval("result:output") let filteredResults = OutputDeclarationFilter().filter(results) - let output = try configuration.outputFormat.formatter.init().format(filteredResults) + let output = try configuration.outputFormat.formatter.init(configuration: configuration).format(filteredResults) logger.info("", canQuiet: true) logger.info(output, canQuiet: false) logger.endInterval(interval) diff --git a/Sources/Frontend/Commands/ScanCommand.swift b/Sources/Frontend/Commands/ScanCommand.swift index 3835a6811..dc75019a4 100644 --- a/Sources/Frontend/Commands/ScanCommand.swift +++ b/Sources/Frontend/Commands/ScanCommand.swift @@ -84,6 +84,9 @@ struct ScanCommand: FrontendCommand { @Flag(help: "Skip the project build step") var skipBuild: Bool = defaultConfiguration.$skipBuild.defaultValue + @Flag(help: "Output result paths relative to the current directory") + var relativeResults: Bool = defaultConfiguration.$relativeResults.defaultValue + @Flag(help: "Exit with non-zero status if any unused code is found") var strict: Bool = defaultConfiguration.$strict.defaultValue @@ -134,6 +137,7 @@ struct ScanCommand: FrontendCommand { configuration.apply(\.$skipBuild, skipBuild) configuration.apply(\.$cleanBuild, cleanBuild) configuration.apply(\.$buildArguments, buildArguments) + configuration.apply(\.$relativeResults, relativeResults) try scanBehavior.main { project in try Scan().perform(project: project) diff --git a/Sources/PeripheryKit/Formatters/CheckstyleFormatter.swift b/Sources/PeripheryKit/Formatters/CheckstyleFormatter.swift index 131adbf4b..48584d089 100644 --- a/Sources/PeripheryKit/Formatters/CheckstyleFormatter.swift +++ b/Sources/PeripheryKit/Formatters/CheckstyleFormatter.swift @@ -1,13 +1,21 @@ import Foundation import Shared +import SystemPackage final class CheckstyleFormatter: OutputFormatter { + let configuration: Configuration + lazy var currentFilePath: FilePath = { .current }() + + init(configuration: Configuration) { + self.configuration = configuration + } + func format(_ results: [ScanResult]) -> String { let parts = results.flatMap { describe($0, colored: false) } let xml = [ "\n", parts - .group(by: { ($0.0.file.path.string).escapedForXML() }) + .group(by: { outputPath($0.0).string.escapedForXML() }) .sorted(by: { $0.key < $1.key }) .map(generateForFile).joined(), "\n" diff --git a/Sources/PeripheryKit/Formatters/CodeClimateFormatter.swift b/Sources/PeripheryKit/Formatters/CodeClimateFormatter.swift index 5804c6186..64b29ed9a 100644 --- a/Sources/PeripheryKit/Formatters/CodeClimateFormatter.swift +++ b/Sources/PeripheryKit/Formatters/CodeClimateFormatter.swift @@ -1,7 +1,15 @@ import Foundation +import Shared +import SystemPackage final class CodeClimateFormatter: OutputFormatter { - + let configuration: Configuration + lazy var currentFilePath: FilePath = { .current }() + + init(configuration: Configuration) { + self.configuration = configuration + } + func format(_ results: [PeripheryKit.ScanResult]) throws -> String { var jsonObject: [Any] = [] @@ -11,7 +19,7 @@ final class CodeClimateFormatter: OutputFormatter { ] let location: [AnyHashable: Any] = [ - "path": result.declaration.location.file.path.url.relativePath, + "path": outputPath(result.declaration.location).url.relativePath, "lines": lines ] @@ -44,5 +52,4 @@ final class CodeClimateFormatter: OutputFormatter { let json = String(data: data, encoding: .utf8) return json ?? "" } - } diff --git a/Sources/PeripheryKit/Formatters/CsvFormatter.swift b/Sources/PeripheryKit/Formatters/CsvFormatter.swift index cd54b5e01..df7f72c60 100644 --- a/Sources/PeripheryKit/Formatters/CsvFormatter.swift +++ b/Sources/PeripheryKit/Formatters/CsvFormatter.swift @@ -1,7 +1,15 @@ import Foundation import Shared +import SystemPackage final class CsvFormatter: OutputFormatter { + let configuration: Configuration + lazy var currentFilePath: FilePath = { .current }() + + init(configuration: Configuration) { + self.configuration = configuration + } + func format(_ results: [ScanResult]) -> String { var lines: [String] = ["Kind,Name,Modifiers,Attributes,Accessibility,IDs,Location,Hints"] @@ -55,6 +63,7 @@ final class CsvFormatter: OutputFormatter { let joinedModifiers = attributes.joined(separator: "|") let joinedAttributes = modifiers.joined(separator: "|") let joinedUsrs = usrs.joined(separator: "|") - return "\(kind),\(name ?? ""),\(joinedModifiers),\(joinedAttributes),\(accessibility ?? ""),\(joinedUsrs),\(location),\(hint ?? "")" + let path = outputPath(location) + return "\(kind),\(name ?? ""),\(joinedModifiers),\(joinedAttributes),\(accessibility ?? ""),\(joinedUsrs),\(path),\(hint ?? "")" } } diff --git a/Sources/PeripheryKit/Formatters/JsonFormatter.swift b/Sources/PeripheryKit/Formatters/JsonFormatter.swift index d2a16ffc9..28036a7e9 100644 --- a/Sources/PeripheryKit/Formatters/JsonFormatter.swift +++ b/Sources/PeripheryKit/Formatters/JsonFormatter.swift @@ -1,7 +1,15 @@ import Foundation import Shared +import SystemPackage final class JsonFormatter: OutputFormatter { + let configuration: Configuration + lazy var currentFilePath: FilePath = { .current }() + + init(configuration: Configuration) { + self.configuration = configuration + } + func format(_ results: [ScanResult]) throws -> String { var jsonObject: [Any] = [] @@ -15,7 +23,7 @@ final class JsonFormatter: OutputFormatter { "accessibility": result.declaration.accessibility.value.rawValue, "ids": Array(result.declaration.usrs), "hints": [describe(result.annotation)], - "location": result.declaration.location.description + "location": outputPath(result.declaration.location).string ] jsonObject.append(object) @@ -30,7 +38,7 @@ final class JsonFormatter: OutputFormatter { "accessibility": "", "ids": [ref.usr], "hints": [redundantConformanceHint], - "location": ref.location.description + "location": outputPath(ref.location).string ] jsonObject.append(object) } diff --git a/Sources/PeripheryKit/Formatters/OutputFormatter.swift b/Sources/PeripheryKit/Formatters/OutputFormatter.swift index b22af5149..d29f28f2d 100644 --- a/Sources/PeripheryKit/Formatters/OutputFormatter.swift +++ b/Sources/PeripheryKit/Formatters/OutputFormatter.swift @@ -1,8 +1,11 @@ import Foundation import Shared +import SystemPackage public protocol OutputFormatter: AnyObject { - init() + var configuration: Configuration { get } + var currentFilePath: FilePath { get } + init(configuration: Configuration) func format(_ results: [ScanResult]) throws -> String } @@ -55,6 +58,16 @@ extension OutputFormatter { return [(result.declaration.location, description)] + secondaryResults } + + func outputPath(_ location: SourceLocation) -> FilePath { + var path = location.file.path.lexicallyNormalized() + + if configuration.relativeResults { + path = path.relativeTo(currentFilePath) + } + + return path + } } public extension OutputFormat { diff --git a/Sources/PeripheryKit/Formatters/XcodeFormatter.swift b/Sources/PeripheryKit/Formatters/XcodeFormatter.swift index ea9f37962..8d75bd7ec 100644 --- a/Sources/PeripheryKit/Formatters/XcodeFormatter.swift +++ b/Sources/PeripheryKit/Formatters/XcodeFormatter.swift @@ -1,7 +1,15 @@ import Foundation import Shared +import SystemPackage final class XcodeFormatter: OutputFormatter { + let configuration: Configuration + lazy var currentFilePath: FilePath = { .current }() + + init(configuration: Configuration) { + self.configuration = configuration + } + func format(_ results: [ScanResult]) throws -> String { guard results.count > 0 else { return colorize("* ", .boldGreen) + colorize("No unused code detected.", .bold) @@ -18,14 +26,14 @@ final class XcodeFormatter: OutputFormatter { // MARK: - Private private func prefix(for location: SourceLocation) -> String { - let absPath = location.file.path.lexicallyNormalized() - let path = absPath.removingLastComponent().string - let file = colorize(absPath.lastComponent?.stem ?? "", .bold) - let ext = absPath.extension ?? "swift" + let path = outputPath(location) + let dir = path.removingLastComponent() + let file = colorize(path.lastComponent?.stem ?? "", .bold) + let ext = path.extension ?? "swift" let lineNum = colorize(String(location.line), .bold) let column = location.column let warning = colorize("warning:", .boldYellow) - return "\(path)/\(file).\(ext):\(lineNum):\(column): \(warning) " + return "\(dir)/\(file).\(ext):\(lineNum):\(column): \(warning) " } } diff --git a/Sources/Shared/Configuration.swift b/Sources/Shared/Configuration.swift index 0b8470a7c..4dc55f446 100644 --- a/Sources/Shared/Configuration.swift +++ b/Sources/Shared/Configuration.swift @@ -92,6 +92,9 @@ public final class Configuration { @Setting(key: "clean_build", defaultValue: false) public var cleanBuild: Bool + @Setting(key: "relative_results", defaultValue: false) + public var relativeResults: Bool + // Non user facing. public var guidedSetup: Bool = false @@ -209,6 +212,10 @@ public final class Configuration { config[$buildArguments.key] = buildArguments } + if $relativeResults.hasNonDefaultValue { + config[$relativeResults.key] = relativeResults + } + return try Yams.dump(object: config) } @@ -279,6 +286,8 @@ public final class Configuration { $cleanBuild.assign(value) case $buildArguments.key: $buildArguments.assign(value) + case $relativeResults.key: + $relativeResults.assign(value) default: logger.warn("\(path.string): invalid key '\(key)'") } @@ -313,6 +322,7 @@ public final class Configuration { $skipBuild.reset() $cleanBuild.reset() $buildArguments.reset() + $relativeResults.reset() } // MARK: - Helpers