mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-16 18:23:53 +01:00
XCode project for app, moved app project to folder
This commit is contained in:
52
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Benchmark.swift
Normal file
52
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Benchmark.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
struct BenchmarkEntry {
|
||||
let id: String
|
||||
let time: Double
|
||||
}
|
||||
|
||||
struct Benchmark {
|
||||
private let name: String
|
||||
private var entries = [BenchmarkEntry]()
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
mutating func record(id: String, time: Double) {
|
||||
guard id != "custom_rules" else { return }
|
||||
entries.append(BenchmarkEntry(id: id, time: time))
|
||||
}
|
||||
|
||||
mutating func record(file: SwiftLintFile, from start: Date) {
|
||||
record(id: file.path ?? "<nopath>", time: -start.timeIntervalSinceNow)
|
||||
}
|
||||
|
||||
func save() {
|
||||
// Decomposed to improve compile times
|
||||
let entriesDict: [String: Double] = entries.reduce(into: [String: Double]()) { accu, idAndTime in
|
||||
accu[idAndTime.id] = (accu[idAndTime.id] ?? 0) + idAndTime.time
|
||||
}
|
||||
let entriesKeyValues: [(String, Double)] = entriesDict.sorted { $0.1 < $1.1 }
|
||||
let lines: [String] = entriesKeyValues.map { id, time -> String in
|
||||
return "\(numberFormatter.string(from: NSNumber(value: time))!): \(id)"
|
||||
}
|
||||
let string: String = lines.joined(separator: "\n") + "\n"
|
||||
let url = URL(fileURLWithPath: "benchmark_\(name)_\(timestamp).txt", isDirectory: false)
|
||||
try? string.data(using: .utf8)?.write(to: url, options: [.atomic])
|
||||
}
|
||||
}
|
||||
|
||||
private let numberFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.minimumFractionDigits = 3
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private let timestamp: String = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy_MM_dd_HH_mm_ss"
|
||||
return formatter.string(from: Date())
|
||||
}()
|
||||
@@ -0,0 +1,108 @@
|
||||
import Foundation
|
||||
|
||||
struct CompilerArgumentsExtractor {
|
||||
static func allCompilerInvocations(compilerLogs: String) -> [[String]] {
|
||||
var compilerInvocations = [[String]]()
|
||||
compilerLogs.enumerateLines { line, _ in
|
||||
if let swiftcIndex = line.range(of: "swiftc ")?.upperBound, line.contains(" -module-name ") {
|
||||
let invocation = parseCLIArguments(String(line[swiftcIndex...]))
|
||||
.expandingResponseFiles
|
||||
.filteringCompilerArguments
|
||||
compilerInvocations.append(invocation)
|
||||
}
|
||||
}
|
||||
return compilerInvocations
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func parseCLIArguments(_ string: String) -> [String] {
|
||||
let escapedSpacePlaceholder = "\u{0}"
|
||||
let scanner = Scanner(string: string)
|
||||
var str = ""
|
||||
var didStart = false
|
||||
while let result = scanner.scanUpToString("\"") {
|
||||
if didStart {
|
||||
str += result.replacingOccurrences(of: " ", with: escapedSpacePlaceholder)
|
||||
str += " "
|
||||
} else {
|
||||
str += result
|
||||
}
|
||||
_ = scanner.scanString("\"")
|
||||
didStart.toggle()
|
||||
}
|
||||
return str.trimmingCharacters(in: .whitespaces)
|
||||
.replacingOccurrences(of: "\\ ", with: escapedSpacePlaceholder)
|
||||
.components(separatedBy: " ")
|
||||
.map { $0.replacingOccurrences(of: escapedSpacePlaceholder, with: " ") }
|
||||
}
|
||||
|
||||
/**
|
||||
Partially filters compiler arguments from `xcodebuild` to something that SourceKit/Clang will accept.
|
||||
|
||||
- parameter args: Compiler arguments, as parsed from `xcodebuild`.
|
||||
|
||||
- returns: A tuple of partially filtered compiler arguments in `.0`, and whether or not there are
|
||||
more flags to remove in `.1`.
|
||||
*/
|
||||
private func partiallyFilter(arguments args: [String]) -> ([String], Bool) {
|
||||
guard let indexOfFlagToRemove = args.firstIndex(of: "-output-file-map") else {
|
||||
return (args, false)
|
||||
}
|
||||
var args = args
|
||||
args.remove(at: args.index(after: indexOfFlagToRemove))
|
||||
args.remove(at: indexOfFlagToRemove)
|
||||
return (args, true)
|
||||
}
|
||||
|
||||
extension Array where Element == String {
|
||||
/// Return the full list of compiler arguments, replacing any response files with their contents.
|
||||
fileprivate var expandingResponseFiles: [String] {
|
||||
return flatMap { arg -> [String] in
|
||||
guard arg.starts(with: "@") else {
|
||||
return [arg]
|
||||
}
|
||||
let responseFile = String(arg.dropFirst())
|
||||
return (try? String(contentsOf: URL(fileURLWithPath: responseFile, isDirectory: false))).flatMap {
|
||||
$0.trimmingCharacters(in: .newlines)
|
||||
.components(separatedBy: "\n")
|
||||
.expandingResponseFiles
|
||||
} ?? [arg]
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns filtered compiler arguments from `xcodebuild` to something that SourceKit/Clang will accept.
|
||||
var filteringCompilerArguments: [String] {
|
||||
var args = self
|
||||
if args.first == "swiftc" {
|
||||
args.removeFirst()
|
||||
}
|
||||
|
||||
// https://github.com/realm/SwiftLint/issues/3365
|
||||
args = args.map { $0.replacingOccurrences(of: "\\=", with: "=") }
|
||||
args = args.map { $0.replacingOccurrences(of: "\\ ", with: " ") }
|
||||
args.append(contentsOf: ["-D", "DEBUG"])
|
||||
var shouldContinueToFilterArguments = true
|
||||
while shouldContinueToFilterArguments {
|
||||
(args, shouldContinueToFilterArguments) = partiallyFilter(arguments: args)
|
||||
}
|
||||
|
||||
return args.filter {
|
||||
![
|
||||
"-parseable-output",
|
||||
"-incremental",
|
||||
"-serialize-diagnostics",
|
||||
"-emit-dependencies",
|
||||
"-use-frontend-parseable-output"
|
||||
].contains($0)
|
||||
}.map {
|
||||
if $0 == "-O" {
|
||||
return "-Onone"
|
||||
} else if $0 == "-DNDEBUG=1" {
|
||||
return "-DDEBUG=1"
|
||||
}
|
||||
return $0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
#endif
|
||||
|
||||
enum ExitHelper {
|
||||
static func successfullyExit() {
|
||||
#if os(Linux)
|
||||
// Workaround for https://github.com/apple/swift/issues/59961
|
||||
Glibc.exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import ArgumentParser
|
||||
|
||||
enum LeniencyOptions: String, EnumerableFlag {
|
||||
case strict, lenient
|
||||
|
||||
static func help(for value: LeniencyOptions) -> ArgumentHelp? {
|
||||
switch value {
|
||||
case .strict:
|
||||
return "Upgrades warnings to serious violations (errors)."
|
||||
case .lenient:
|
||||
return "Downgrades serious violations to warnings, warning threshold is disabled."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Common Arguments
|
||||
|
||||
struct LintOrAnalyzeArguments: ParsableArguments {
|
||||
@Option(help: "The path to one or more SwiftLint configuration files, evaluated as a parent-child hierarchy.")
|
||||
var config = [String]()
|
||||
@Flag(name: [.long, .customLong("autocorrect")], help: "Correct violations whenever possible.")
|
||||
var fix = false
|
||||
@Flag(help: """
|
||||
Should reformat the Swift files using the same mechanism used by Xcode (via SourceKit).
|
||||
Only applied with `--fix`/`--autocorrect`.
|
||||
""")
|
||||
var format = false
|
||||
@Flag(help: "Use an alternative algorithm to exclude paths for `excluded`, which may be faster in some cases.")
|
||||
var useAlternativeExcluding = false
|
||||
@Flag(help: "Read SCRIPT_INPUT_FILE* environment variables as files.")
|
||||
var useScriptInputFiles = false
|
||||
@Flag(exclusivity: .exclusive)
|
||||
var leniency: LeniencyOptions?
|
||||
@Flag(help: "Exclude files in config `excluded` even if their paths are explicitly specified.")
|
||||
var forceExclude = false
|
||||
@Flag(help: "Save benchmarks to `benchmark_files.txt` and `benchmark_rules.txt`.")
|
||||
var benchmark = false
|
||||
@Option(help: "The reporter used to log errors and warnings.")
|
||||
var reporter: String?
|
||||
@Flag(help: "Use the in-process version of SourceKit.")
|
||||
var inProcessSourcekit = false
|
||||
@Option(help: "The file where violations should be saved. Prints to stdout by default.")
|
||||
var output: String?
|
||||
@Flag(help: "Show a live-updating progress bar instead of each file being processed.")
|
||||
var progress = false
|
||||
}
|
||||
|
||||
// MARK: - Common Argument Help
|
||||
|
||||
// It'd be great to be able to parameterize an `@OptionGroup` so we could move these options into
|
||||
// `LintOrAnalyzeArguments`.
|
||||
|
||||
func pathOptionDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||
ArgumentHelp(visibility: .hidden)
|
||||
}
|
||||
|
||||
func pathsArgumentDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||
"List of paths to the files or directories to \(mode.imperative)."
|
||||
}
|
||||
|
||||
func quietOptionDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||
"Don't print status logs like '\(mode.verb.capitalized) <file>' & 'Done \(mode.verb)'."
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
import Dispatch
|
||||
import Foundation
|
||||
@_spi(TestHelper)
|
||||
import SwiftLintFramework
|
||||
|
||||
enum LintOrAnalyzeMode {
|
||||
case lint, analyze
|
||||
|
||||
var imperative: String {
|
||||
switch self {
|
||||
case .lint:
|
||||
return "lint"
|
||||
case .analyze:
|
||||
return "analyze"
|
||||
}
|
||||
}
|
||||
|
||||
var verb: String {
|
||||
switch self {
|
||||
case .lint:
|
||||
return "linting"
|
||||
case .analyze:
|
||||
return "analyzing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintOrAnalyzeCommand {
|
||||
static func run(_ options: LintOrAnalyzeOptions) async throws {
|
||||
if options.inProcessSourcekit {
|
||||
queuedPrintError(
|
||||
"""
|
||||
warning: The --in-process-sourcekit option is deprecated. \
|
||||
SwiftLint now always uses an in-process SourceKit.
|
||||
"""
|
||||
)
|
||||
}
|
||||
try await Signposts.record(name: "LintOrAnalyzeCommand.run") {
|
||||
try await options.autocorrect ? autocorrect(options) : lintOrAnalyze(options)
|
||||
}
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
|
||||
private static func lintOrAnalyze(_ options: LintOrAnalyzeOptions) async throws {
|
||||
let builder = LintOrAnalyzeResultBuilder(options)
|
||||
let files = try await collectViolations(builder: builder)
|
||||
try Signposts.record(name: "LintOrAnalyzeCommand.PostProcessViolations") {
|
||||
try postProcessViolations(files: files, builder: builder)
|
||||
}
|
||||
}
|
||||
|
||||
private static func collectViolations(builder: LintOrAnalyzeResultBuilder) async throws -> [SwiftLintFile] {
|
||||
let options = builder.options
|
||||
let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation")
|
||||
return try await builder.configuration.visitLintableFiles(options: options, cache: builder.cache,
|
||||
storage: builder.storage) { linter in
|
||||
let currentViolations: [StyleViolation]
|
||||
if options.benchmark {
|
||||
CustomRuleTimer.shared.activate()
|
||||
let start = Date()
|
||||
let (violationsBeforeLeniency, currentRuleTimes) = linter
|
||||
.styleViolationsAndRuleTimes(using: builder.storage)
|
||||
currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency)
|
||||
visitorMutationQueue.sync {
|
||||
builder.fileBenchmark.record(file: linter.file, from: start)
|
||||
currentRuleTimes.forEach { builder.ruleBenchmark.record(id: $0, time: $1) }
|
||||
builder.violations += currentViolations
|
||||
}
|
||||
} else {
|
||||
currentViolations = applyLeniency(options: options,
|
||||
violations: linter.styleViolations(using: builder.storage))
|
||||
visitorMutationQueue.sync {
|
||||
builder.violations += currentViolations
|
||||
}
|
||||
}
|
||||
linter.file.invalidateCache()
|
||||
builder.report(violations: currentViolations, realtimeCondition: true)
|
||||
}
|
||||
}
|
||||
|
||||
private static func postProcessViolations(files: [SwiftLintFile], builder: LintOrAnalyzeResultBuilder) throws {
|
||||
let options = builder.options
|
||||
let configuration = builder.configuration
|
||||
if isWarningThresholdBroken(configuration: configuration, violations: builder.violations)
|
||||
&& !options.lenient {
|
||||
builder.violations.append(
|
||||
createThresholdViolation(threshold: configuration.warningThreshold!)
|
||||
)
|
||||
builder.report(violations: [builder.violations.last!], realtimeCondition: true)
|
||||
}
|
||||
builder.report(violations: builder.violations, realtimeCondition: false)
|
||||
let numberOfSeriousViolations = builder.violations.filter({ $0.severity == .error }).count
|
||||
if !options.quiet {
|
||||
printStatus(violations: builder.violations, files: files, serious: numberOfSeriousViolations,
|
||||
verb: options.verb)
|
||||
}
|
||||
if options.benchmark {
|
||||
builder.fileBenchmark.save()
|
||||
for (id, time) in CustomRuleTimer.shared.dump() {
|
||||
builder.ruleBenchmark.record(id: id, time: time)
|
||||
}
|
||||
builder.ruleBenchmark.save()
|
||||
if !options.quiet, let memoryUsage = memoryUsage() {
|
||||
queuedPrintError(memoryUsage)
|
||||
}
|
||||
}
|
||||
try builder.cache?.save()
|
||||
guard numberOfSeriousViolations == 0 else { exit(2) }
|
||||
}
|
||||
|
||||
private static func printStatus(violations: [StyleViolation], files: [SwiftLintFile], serious: Int, verb: String) {
|
||||
let pluralSuffix = { (collection: [Any]) -> String in
|
||||
return collection.count != 1 ? "s" : ""
|
||||
}
|
||||
queuedPrintError(
|
||||
"Done \(verb)! Found \(violations.count) violation\(pluralSuffix(violations)), " +
|
||||
"\(serious) serious in \(files.count) file\(pluralSuffix(files))."
|
||||
)
|
||||
}
|
||||
|
||||
private static func isWarningThresholdBroken(configuration: Configuration,
|
||||
violations: [StyleViolation]) -> Bool {
|
||||
guard let warningThreshold = configuration.warningThreshold else { return false }
|
||||
let numberOfWarningViolations = violations.filter({ $0.severity == .warning }).count
|
||||
return numberOfWarningViolations >= warningThreshold
|
||||
}
|
||||
|
||||
private static func createThresholdViolation(threshold: Int) -> StyleViolation {
|
||||
let description = RuleDescription(
|
||||
identifier: "warning_threshold",
|
||||
name: "Warning Threshold",
|
||||
description: "Number of warnings thrown is above the threshold",
|
||||
kind: .lint
|
||||
)
|
||||
return StyleViolation(
|
||||
ruleDescription: description,
|
||||
severity: .error,
|
||||
location: Location(file: "", line: 0, character: 0),
|
||||
reason: "Number of warnings exceeded threshold of \(threshold).")
|
||||
}
|
||||
|
||||
private static func applyLeniency(options: LintOrAnalyzeOptions, violations: [StyleViolation]) -> [StyleViolation] {
|
||||
switch (options.lenient, options.strict) {
|
||||
case (false, false):
|
||||
return violations
|
||||
|
||||
case (true, false):
|
||||
return violations.map {
|
||||
if $0.severity == .error {
|
||||
return $0.with(severity: .warning)
|
||||
} else {
|
||||
return $0
|
||||
}
|
||||
}
|
||||
|
||||
case (false, true):
|
||||
return violations.map {
|
||||
if $0.severity == .warning {
|
||||
return $0.with(severity: .error)
|
||||
} else {
|
||||
return $0
|
||||
}
|
||||
}
|
||||
|
||||
case (true, true):
|
||||
queuedFatalError("Invalid command line options: 'lenient' and 'strict' are mutually exclusive.")
|
||||
}
|
||||
}
|
||||
|
||||
private static func autocorrect(_ options: LintOrAnalyzeOptions) async throws {
|
||||
let storage = RuleStorage()
|
||||
let configuration = Configuration(options: options)
|
||||
let correctionsBuilder = CorrectionsBuilder()
|
||||
let files = try await configuration
|
||||
.visitLintableFiles(options: options, cache: nil, storage: storage) { linter in
|
||||
if options.format {
|
||||
switch configuration.indentation {
|
||||
case .tabs:
|
||||
linter.format(useTabs: true, indentWidth: 4)
|
||||
case .spaces(let count):
|
||||
linter.format(useTabs: false, indentWidth: count)
|
||||
}
|
||||
}
|
||||
|
||||
let corrections = linter.correct(using: storage)
|
||||
if !corrections.isEmpty && !options.quiet {
|
||||
if options.useSTDIN {
|
||||
queuedPrint(linter.file.contents)
|
||||
} else {
|
||||
if options.progress {
|
||||
await correctionsBuilder.append(corrections)
|
||||
} else {
|
||||
let correctionLogs = corrections.map(\.consoleDescription)
|
||||
queuedPrint(correctionLogs.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !options.quiet {
|
||||
if options.progress {
|
||||
let corrections = await correctionsBuilder.corrections
|
||||
if !corrections.isEmpty {
|
||||
let correctionLogs = corrections.map(\.consoleDescription)
|
||||
options.writeToOutput(correctionLogs.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
let pluralSuffix = { (collection: [Any]) -> String in
|
||||
return collection.count != 1 ? "s" : ""
|
||||
}
|
||||
queuedPrintError("Done correcting \(files.count) file\(pluralSuffix(files))!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintOrAnalyzeOptions {
|
||||
let mode: LintOrAnalyzeMode
|
||||
let paths: [String]
|
||||
let useSTDIN: Bool
|
||||
let configurationFiles: [String]
|
||||
let strict: Bool
|
||||
let lenient: Bool
|
||||
let forceExclude: Bool
|
||||
let useExcludingByPrefix: Bool
|
||||
let useScriptInputFiles: Bool
|
||||
let benchmark: Bool
|
||||
let reporter: String?
|
||||
let quiet: Bool
|
||||
let output: String?
|
||||
let progress: Bool
|
||||
let cachePath: String?
|
||||
let ignoreCache: Bool
|
||||
let enableAllRules: Bool
|
||||
let autocorrect: Bool
|
||||
let format: Bool
|
||||
let compilerLogPath: String?
|
||||
let compileCommands: String?
|
||||
let inProcessSourcekit: Bool
|
||||
|
||||
var verb: String {
|
||||
if autocorrect {
|
||||
return "correcting"
|
||||
} else {
|
||||
return mode.verb
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LintOrAnalyzeResultBuilder {
|
||||
var fileBenchmark = Benchmark(name: "files")
|
||||
var ruleBenchmark = Benchmark(name: "rules")
|
||||
var violations = [StyleViolation]()
|
||||
let storage = RuleStorage()
|
||||
let configuration: Configuration
|
||||
let reporter: Reporter.Type
|
||||
let cache: LinterCache?
|
||||
let options: LintOrAnalyzeOptions
|
||||
|
||||
init(_ options: LintOrAnalyzeOptions) {
|
||||
let config = Signposts.record(name: "LintOrAnalyzeCommand.ParseConfiguration") {
|
||||
Configuration(options: options)
|
||||
}
|
||||
configuration = config
|
||||
reporter = reporterFrom(identifier: options.reporter ?? config.reporter)
|
||||
if options.ignoreCache || ProcessInfo.processInfo.isLikelyXcodeCloudEnvironment {
|
||||
cache = nil
|
||||
} else {
|
||||
cache = LinterCache(configuration: config)
|
||||
}
|
||||
self.options = options
|
||||
|
||||
if let outFile = options.output {
|
||||
do {
|
||||
try Data().write(to: URL(fileURLWithPath: outFile))
|
||||
} catch {
|
||||
queuedPrintError("Could not write to file at path \(outFile)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func report(violations: [StyleViolation], realtimeCondition: Bool) {
|
||||
if (reporter.isRealtime && (!options.progress || options.output != nil)) == realtimeCondition {
|
||||
let report = reporter.generateReport(violations)
|
||||
if !report.isEmpty {
|
||||
options.writeToOutput(report)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension LintOrAnalyzeOptions {
|
||||
func writeToOutput(_ string: String) {
|
||||
guard let outFile = output else {
|
||||
queuedPrint(string)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let outFileURL = URL(fileURLWithPath: outFile)
|
||||
let fileUpdater = try FileHandle(forUpdating: outFileURL)
|
||||
fileUpdater.seekToEndOfFile()
|
||||
fileUpdater.write(Data((string + "\n").utf8))
|
||||
fileUpdater.closeFile()
|
||||
} catch {
|
||||
queuedPrintError("Could not write to file at path \(outFile)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private actor CorrectionsBuilder {
|
||||
private(set) var corrections: [Correction] = []
|
||||
|
||||
func append(_ corrections: [Correction]) {
|
||||
self.corrections.append(contentsOf: corrections)
|
||||
}
|
||||
}
|
||||
|
||||
private func memoryUsage() -> String? {
|
||||
#if os(Linux)
|
||||
return nil
|
||||
#else
|
||||
var info = mach_task_basic_info()
|
||||
let basicInfoCount = MemoryLayout<mach_task_basic_info>.stride / MemoryLayout<natural_t>.stride
|
||||
var count = mach_msg_type_number_t(basicInfoCount)
|
||||
|
||||
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: basicInfoCount) {
|
||||
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
|
||||
}
|
||||
}
|
||||
|
||||
if kerr == KERN_SUCCESS {
|
||||
let bytes = Measurement<UnitInformationStorage>(value: Double(info.resident_size), unit: .bytes)
|
||||
let formatted = ByteCountFormatter().string(from: bytes)
|
||||
return "Memory used: \(formatted)"
|
||||
} else {
|
||||
let errorMessage = String(cString: mach_error_string(kerr), encoding: .ascii)
|
||||
return "Error with task_info(): \(errorMessage ?? "unknown")"
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
import SwiftLintFramework
|
||||
|
||||
typealias File = String
|
||||
typealias Arguments = [String]
|
||||
|
||||
class CompilerInvocations {
|
||||
static func buildLog(compilerInvocations: [[String]]) -> CompilerInvocations {
|
||||
return ArrayCompilerInvocations(invocations: compilerInvocations)
|
||||
}
|
||||
|
||||
static func compilationDatabase(compileCommands: [File: Arguments]) -> CompilerInvocations {
|
||||
return CompilationDatabaseInvocations(compileCommands: compileCommands)
|
||||
}
|
||||
|
||||
/// Default implementation
|
||||
func arguments(forFile path: String?) -> Arguments { [] }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private class ArrayCompilerInvocations: CompilerInvocations {
|
||||
private let invocationsByArgument: [String: [Arguments]]
|
||||
|
||||
init(invocations: [Arguments]) {
|
||||
// Store invocations by the path, so next when we'll be asked for arguments,
|
||||
// we'll be able to return them faster
|
||||
self.invocationsByArgument = invocations.reduce(into: [:]) { result, arguments in
|
||||
arguments.forEach { result[$0, default: []].append(arguments) }
|
||||
}
|
||||
}
|
||||
|
||||
override func arguments(forFile path: String?) -> Arguments {
|
||||
return path.flatMap { path in
|
||||
return invocationsByArgument[path]?.first
|
||||
} ?? []
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationDatabaseInvocations: CompilerInvocations {
|
||||
private let compileCommands: [File: Arguments]
|
||||
|
||||
init(compileCommands: [File: Arguments]) {
|
||||
self.compileCommands = compileCommands
|
||||
}
|
||||
|
||||
override func arguments(forFile path: String?) -> Arguments {
|
||||
return path.flatMap { path in
|
||||
return compileCommands[path] ??
|
||||
compileCommands[path.path(relativeTo: FileManager.default.currentDirectoryPath)]
|
||||
} ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LintOrAnalyzeModeWithCompilerArguments {
|
||||
case lint
|
||||
case analyze(allCompilerInvocations: CompilerInvocations)
|
||||
}
|
||||
|
||||
private func resolveParamsFiles(args: [String]) -> [String] {
|
||||
return args.reduce(into: []) { (allArgs: inout [String], arg: String) -> Void in
|
||||
if arg.hasPrefix("@"), let contents = try? String(contentsOfFile: String(arg.dropFirst())) {
|
||||
allArgs.append(contentsOf: resolveParamsFiles(args: contents.split(separator: "\n").map(String.init)))
|
||||
} else {
|
||||
allArgs.append(arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintableFilesVisitor {
|
||||
let paths: [String]
|
||||
let action: String
|
||||
let useSTDIN: Bool
|
||||
let quiet: Bool
|
||||
let showProgressBar: Bool
|
||||
let useScriptInputFiles: Bool
|
||||
let forceExclude: Bool
|
||||
let useExcludingByPrefix: Bool
|
||||
let cache: LinterCache?
|
||||
let parallel: Bool
|
||||
let allowZeroLintableFiles: Bool
|
||||
let mode: LintOrAnalyzeModeWithCompilerArguments
|
||||
let block: (CollectedLinter) async -> Void
|
||||
|
||||
private init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, showProgressBar: Bool,
|
||||
useScriptInputFiles: Bool, forceExclude: Bool, useExcludingByPrefix: Bool,
|
||||
cache: LinterCache?, compilerInvocations: CompilerInvocations?,
|
||||
allowZeroLintableFiles: Bool, block: @escaping (CollectedLinter) async -> Void) {
|
||||
self.paths = resolveParamsFiles(args: paths)
|
||||
self.action = action
|
||||
self.useSTDIN = useSTDIN
|
||||
self.quiet = quiet
|
||||
self.showProgressBar = showProgressBar
|
||||
self.useScriptInputFiles = useScriptInputFiles
|
||||
self.forceExclude = forceExclude
|
||||
self.useExcludingByPrefix = useExcludingByPrefix
|
||||
self.cache = cache
|
||||
if let compilerInvocations {
|
||||
self.mode = .analyze(allCompilerInvocations: compilerInvocations)
|
||||
// SourceKit had some changes in 5.6 that makes it ~100x more expensive
|
||||
// to process files concurrently. By processing files serially, it's
|
||||
// only 2x slower than before.
|
||||
self.parallel = SwiftVersion.current < .fiveDotSix
|
||||
} else {
|
||||
self.mode = .lint
|
||||
self.parallel = true
|
||||
}
|
||||
self.block = block
|
||||
self.allowZeroLintableFiles = allowZeroLintableFiles
|
||||
}
|
||||
|
||||
static func create(_ options: LintOrAnalyzeOptions,
|
||||
cache: LinterCache?,
|
||||
allowZeroLintableFiles: Bool,
|
||||
block: @escaping (CollectedLinter) async -> Void)
|
||||
throws -> LintableFilesVisitor {
|
||||
try Signposts.record(name: "LintableFilesVisitor.Create") {
|
||||
let compilerInvocations: CompilerInvocations?
|
||||
if options.mode == .lint {
|
||||
compilerInvocations = nil
|
||||
} else {
|
||||
compilerInvocations = try loadCompilerInvocations(options)
|
||||
}
|
||||
|
||||
return LintableFilesVisitor(
|
||||
paths: options.paths, action: options.verb.bridge().capitalized,
|
||||
useSTDIN: options.useSTDIN, quiet: options.quiet,
|
||||
showProgressBar: options.progress,
|
||||
useScriptInputFiles: options.useScriptInputFiles,
|
||||
forceExclude: options.forceExclude,
|
||||
useExcludingByPrefix: options.useExcludingByPrefix,
|
||||
cache: cache,
|
||||
compilerInvocations: compilerInvocations,
|
||||
allowZeroLintableFiles: allowZeroLintableFiles, block: block
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func shouldSkipFile(atPath path: String?) -> Bool {
|
||||
switch self.mode {
|
||||
case .lint:
|
||||
return false
|
||||
case let .analyze(compilerInvocations):
|
||||
let compilerArguments = compilerInvocations.arguments(forFile: path)
|
||||
return compilerArguments.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
func linter(forFile file: SwiftLintFile, configuration: Configuration) -> Linter {
|
||||
switch self.mode {
|
||||
case .lint:
|
||||
return Linter(file: file, configuration: configuration, cache: cache)
|
||||
case let .analyze(compilerInvocations):
|
||||
let compilerArguments = compilerInvocations.arguments(forFile: file.path)
|
||||
return Linter(file: file, configuration: configuration, compilerArguments: compilerArguments)
|
||||
}
|
||||
}
|
||||
|
||||
private static func loadCompilerInvocations(_ options: LintOrAnalyzeOptions) throws -> CompilerInvocations {
|
||||
if let path = options.compilerLogPath {
|
||||
guard let compilerInvocations = self.loadLogCompilerInvocations(path) else {
|
||||
throw SwiftLintError.usageError(description: "Could not read compiler log at path: '\(path)'")
|
||||
}
|
||||
|
||||
return .buildLog(compilerInvocations: compilerInvocations)
|
||||
} else if let path = options.compileCommands {
|
||||
do {
|
||||
return .compilationDatabase(compileCommands: try self.loadCompileCommands(path))
|
||||
} catch {
|
||||
throw SwiftLintError.usageError(
|
||||
description: "Could not read compilation database at path: '\(path)' \(error.localizedDescription)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
throw SwiftLintError.usageError(description: "Could not read compiler invocations")
|
||||
}
|
||||
|
||||
private static func loadLogCompilerInvocations(_ path: String) -> [[String]]? {
|
||||
if let data = FileManager.default.contents(atPath: path),
|
||||
let logContents = String(data: data, encoding: .utf8) {
|
||||
if logContents.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CompilerArgumentsExtractor.allCompilerInvocations(compilerLogs: logContents)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func loadCompileCommands(_ path: String) throws -> [File: Arguments] {
|
||||
guard let fileContents = FileManager.default.contents(atPath: path) else {
|
||||
throw CompileCommandsLoadError.nonExistentFile(path)
|
||||
}
|
||||
|
||||
if path.hasSuffix(".yaml") || path.hasSuffix(".yml") {
|
||||
// Assume this is a SwiftPM yaml file
|
||||
return try SwiftPMCompilationDB.parse(yaml: fileContents)
|
||||
}
|
||||
|
||||
guard let object = try? JSONSerialization.jsonObject(with: fileContents),
|
||||
let compileDB = object as? [[String: Any]] else {
|
||||
throw CompileCommandsLoadError.malformedCommands(path)
|
||||
}
|
||||
|
||||
// Convert the compilation database to a dictionary, with source files as keys and compiler arguments as values.
|
||||
//
|
||||
// Compilation databases are an array of dictionaries. Each dict has "file" and "arguments" keys.
|
||||
var commands = [File: Arguments]()
|
||||
for (index, entry) in compileDB.enumerated() {
|
||||
guard let file = entry["file"] as? String else {
|
||||
throw CompileCommandsLoadError.malformedFile(path, index)
|
||||
}
|
||||
|
||||
guard let arguments = entry["arguments"] as? [String] else {
|
||||
throw CompileCommandsLoadError.malformedArguments(path, index)
|
||||
}
|
||||
|
||||
guard arguments.contains(file) else {
|
||||
throw CompileCommandsLoadError.missingFileInArguments(path, index, arguments)
|
||||
}
|
||||
|
||||
commands[file] = arguments.filteringCompilerArguments
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
|
||||
private enum CompileCommandsLoadError: LocalizedError {
|
||||
case nonExistentFile(String)
|
||||
case malformedCommands(String)
|
||||
case malformedFile(String, Int)
|
||||
case malformedArguments(String, Int)
|
||||
case missingFileInArguments(String, Int, [String])
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case let .nonExistentFile(path):
|
||||
return "Could not read compile commands file at '\(path)'"
|
||||
case let .malformedCommands(path):
|
||||
return "Compile commands file at '\(path)' isn't in the correct format"
|
||||
case let .malformedFile(path, index):
|
||||
return "Missing or invalid (must be a string) 'file' key in \(path) at index \(index)"
|
||||
case let .malformedArguments(path, index):
|
||||
return "Missing or invalid (must be an array of strings) 'arguments' key in \(path) at index \(index)"
|
||||
case let .missingFileInArguments(path, index, arguments):
|
||||
return "Entry in \(path) at index \(index) has 'arguments' which do not contain the 'file': \(arguments)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import Dispatch
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
// Inspired by https://github.com/jkandzi/Progress.swift
|
||||
actor ProgressBar {
|
||||
private var index = 1
|
||||
private var lastPrintedTime: TimeInterval = 0.0
|
||||
private let startTime = uptime()
|
||||
private let count: Int
|
||||
|
||||
init(count: Int) {
|
||||
self.count = count
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
// When progress is printed, the previous line is reset, so print an empty line before anything else
|
||||
queuedPrintError("")
|
||||
}
|
||||
|
||||
func printNext() {
|
||||
guard index <= count else { return }
|
||||
|
||||
let currentTime = uptime()
|
||||
if currentTime - lastPrintedTime > 0.1 || index == count {
|
||||
let lineReset = "\u{1B}[1A\u{1B}[K"
|
||||
let bar = makeBar()
|
||||
let timeEstimate = makeTimeEstimate(currentTime: currentTime)
|
||||
let lineContents = "\(index) of \(count) \(bar) \(timeEstimate)"
|
||||
queuedPrintError("\(lineReset)\(lineContents)")
|
||||
lastPrintedTime = currentTime
|
||||
}
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func makeBar() -> String {
|
||||
let barLength = 30
|
||||
let completedBarElements = Int(Double(barLength) * (Double(index) / Double(count)))
|
||||
let barArray = Array(repeating: "=", count: completedBarElements) +
|
||||
Array(repeating: " ", count: barLength - completedBarElements)
|
||||
return "[\(barArray.joined())]"
|
||||
}
|
||||
|
||||
private func makeTimeEstimate(currentTime: TimeInterval) -> String {
|
||||
let totalTime = currentTime - startTime
|
||||
let itemsPerSecond = Double(index) / totalTime
|
||||
let estimatedTimeRemaining = Double(count - index) / itemsPerSecond
|
||||
let estimatedTimeRemainingString = "\(Int(estimatedTimeRemaining))s"
|
||||
return "ETA: \(estimatedTimeRemainingString) (\(Int(itemsPerSecond)) files/s)"
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
// swiftlint:disable:next identifier_name
|
||||
private let NSEC_PER_SEC = 1_000_000_000
|
||||
#endif
|
||||
|
||||
private func uptime() -> TimeInterval {
|
||||
Double(DispatchTime.now().uptimeNanoseconds) / Double(NSEC_PER_SEC)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@_spi(TestHelper)
|
||||
import SwiftLintFramework
|
||||
|
||||
extension RulesFilter {
|
||||
struct ExcludingOptions: OptionSet {
|
||||
let rawValue: Int
|
||||
|
||||
static let enabled = Self(rawValue: 1 << 0)
|
||||
static let disabled = Self(rawValue: 1 << 1)
|
||||
static let uncorrectable = Self(rawValue: 1 << 2)
|
||||
}
|
||||
}
|
||||
|
||||
class RulesFilter {
|
||||
private let allRules: RuleList
|
||||
private let enabledRules: [Rule]
|
||||
|
||||
init(allRules: RuleList = primaryRuleList, enabledRules: [Rule]) {
|
||||
self.allRules = allRules
|
||||
self.enabledRules = enabledRules
|
||||
}
|
||||
|
||||
func getRules(excluding excludingOptions: ExcludingOptions) -> RuleList {
|
||||
if excludingOptions.isEmpty {
|
||||
return allRules
|
||||
}
|
||||
|
||||
let filtered: [Rule.Type] = allRules.list.compactMap { ruleID, ruleType in
|
||||
let enabledRule = enabledRules.first { rule in
|
||||
type(of: rule).description.identifier == ruleID
|
||||
}
|
||||
let isRuleEnabled = enabledRule != nil
|
||||
|
||||
if excludingOptions.contains(.enabled) && isRuleEnabled {
|
||||
return nil
|
||||
}
|
||||
if excludingOptions.contains(.disabled) && !isRuleEnabled {
|
||||
return nil
|
||||
}
|
||||
if excludingOptions.contains(.uncorrectable) && !(ruleType is CorrectableRule.Type) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ruleType
|
||||
}
|
||||
|
||||
return RuleList(rules: filtered)
|
||||
}
|
||||
}
|
||||
73
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Signposts.swift
Normal file
73
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Signposts.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
#if canImport(os)
|
||||
import os.signpost
|
||||
private let timelineLog = OSLog(subsystem: "io.realm.swiftlint", category: "Timeline")
|
||||
private let fileLog = OSLog(subsystem: "io.realm.swiftlint", category: "File")
|
||||
#endif
|
||||
|
||||
struct Signposts {
|
||||
enum Span {
|
||||
case timeline, file(String)
|
||||
}
|
||||
|
||||
static func record<R>(name: StaticString, span: Span = .timeline, body: () throws -> R) rethrows -> R {
|
||||
#if canImport(os)
|
||||
let log: OSLog
|
||||
let description: String?
|
||||
switch span {
|
||||
case .timeline:
|
||||
log = timelineLog
|
||||
description = nil
|
||||
case .file(let file):
|
||||
log = fileLog
|
||||
description = file
|
||||
}
|
||||
let signpostID = OSSignpostID(log: log)
|
||||
if let description {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
|
||||
let result = try body()
|
||||
if let description {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
return result
|
||||
#else
|
||||
return try body()
|
||||
#endif
|
||||
}
|
||||
|
||||
static func record<R>(name: StaticString, span: Span = .timeline, body: () async throws -> R) async rethrows -> R {
|
||||
#if canImport(os)
|
||||
let log: OSLog
|
||||
let description: String?
|
||||
switch span {
|
||||
case .timeline:
|
||||
log = timelineLog
|
||||
description = nil
|
||||
case .file(let file):
|
||||
log = fileLog
|
||||
description = file
|
||||
}
|
||||
let signpostID = OSSignpostID(log: log)
|
||||
if let description {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
|
||||
let result = try await body()
|
||||
if let description {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
return result
|
||||
#else
|
||||
return try await body()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
enum SwiftLintError: LocalizedError {
|
||||
case usageError(description: String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .usageError(let description):
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import Foundation
|
||||
import Yams
|
||||
|
||||
private struct SwiftPMCommand: Codable {
|
||||
let tool: String
|
||||
let module: String?
|
||||
let sources: [String]?
|
||||
let args: [String]?
|
||||
let importPaths: [String]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case tool
|
||||
case module = "module-name"
|
||||
case sources
|
||||
case args = "other-args"
|
||||
case importPaths = "import-paths"
|
||||
}
|
||||
}
|
||||
|
||||
private struct SwiftPMNode: Codable {}
|
||||
|
||||
private struct SwiftPMNodes: Codable {
|
||||
let nodes: [String: SwiftPMNode]
|
||||
}
|
||||
|
||||
struct SwiftPMCompilationDB: Codable {
|
||||
private let commands: [String: SwiftPMCommand]
|
||||
|
||||
static func parse(yaml: Data) throws -> [File: Arguments] {
|
||||
let decoder = YAMLDecoder()
|
||||
let compilationDB: SwiftPMCompilationDB
|
||||
|
||||
if ProcessInfo.processInfo.environment["TEST_SRCDIR"] != nil {
|
||||
// Running tests
|
||||
let nodes = try decoder.decode(SwiftPMNodes.self, from: yaml)
|
||||
let suffix = "/Source/swiftlint/"
|
||||
let pathToReplace = Array(nodes.nodes.keys.filter({ node in
|
||||
node.hasSuffix(suffix)
|
||||
}))[0].dropLast(suffix.count - 1)
|
||||
let stringFileContents = String(data: yaml, encoding: .utf8)!
|
||||
.replacingOccurrences(of: pathToReplace, with: "")
|
||||
compilationDB = try decoder.decode(Self.self, from: stringFileContents)
|
||||
} else {
|
||||
compilationDB = try decoder.decode(Self.self, from: yaml)
|
||||
}
|
||||
|
||||
let swiftCompilerCommands = compilationDB.commands
|
||||
.filter { $0.value.tool == "swift-compiler" }
|
||||
let allSwiftSources = swiftCompilerCommands
|
||||
.flatMap { $0.value.sources ?? [] }
|
||||
.filter { $0.hasSuffix(".swift") }
|
||||
return Dictionary(uniqueKeysWithValues: allSwiftSources.map { swiftSource in
|
||||
let command = swiftCompilerCommands
|
||||
.values
|
||||
.first { $0.sources?.contains(swiftSource) == true }
|
||||
|
||||
guard let command,
|
||||
let module = command.module,
|
||||
let sources = command.sources,
|
||||
let arguments = command.args,
|
||||
let importPaths = command.importPaths
|
||||
else {
|
||||
return (swiftSource, [])
|
||||
}
|
||||
|
||||
let args = ["-module-name", module] +
|
||||
sources +
|
||||
arguments.filteringCompilerArguments +
|
||||
["-I"] + importPaths
|
||||
|
||||
return (swiftSource, args)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user