SwiftLint/Source/swiftlint/Commands/RulesCommand.swift

171 lines
6.2 KiB
Swift

import Commandant
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
#error("Unsupported platform")
#endif
import Result
import SwiftLintFramework
import SwiftyTextTable
private func print(ruleDescription desc: RuleDescription) {
print("\(desc.consoleDescription)")
if !desc.triggeringExamples.isEmpty {
func indent(_ string: String) -> String {
return string.components(separatedBy: "\n")
.map { " \($0)" }
.joined(separator: "\n")
}
print("\nTriggering Examples (violation is marked with '↓'):")
for (index, example) in desc.triggeringExamples.enumerated() {
print("\nExample #\(index + 1)\n\n\(indent(example))")
}
}
}
struct RulesCommand: CommandProtocol {
let verb = "rules"
let function = "Display the list of rules and their identifiers"
func run(_ options: RulesOptions) -> Result<(), CommandantError<()>> {
if let ruleID = options.ruleID {
guard let rule = masterRuleList.list[ruleID] else {
return .failure(.usageError(description: "No rule with identifier: \(ruleID)"))
}
print(ruleDescription: rule.description)
return .success(())
}
if options.onlyDisabledRules && options.onlyEnabledRules {
return .failure(.usageError(description: "You can't use --disabled and --enabled at the same time."))
}
let configuration = Configuration(options: options)
let rules = ruleList(for: options, configuration: configuration)
print(TextTable(ruleList: rules, configuration: configuration).render())
return .success(())
}
private func ruleList(for options: RulesOptions, configuration: Configuration) -> RuleList {
guard options.onlyEnabledRules || options.onlyDisabledRules else {
return masterRuleList
}
let filtered: [Rule.Type] = masterRuleList.list.compactMap { ruleID, ruleType in
let configuredRule = configuration.rules.first { rule in
return type(of: rule).description.identifier == ruleID
}
if options.onlyEnabledRules && configuredRule == nil {
return nil
} else if options.onlyDisabledRules && configuredRule != nil {
return nil
}
return ruleType
}
return RuleList(rules: filtered)
}
}
struct RulesOptions: OptionsProtocol {
fileprivate let ruleID: String?
let configurationFile: String
fileprivate let onlyEnabledRules: Bool
fileprivate let onlyDisabledRules: Bool
// swiftlint:disable line_length
static func create(_ configurationFile: String) -> (_ ruleID: String) -> (_ onlyEnabledRules: Bool) -> (_ onlyDisabledRules: Bool) -> RulesOptions {
return { ruleID in { onlyEnabledRules in { onlyDisabledRules in
self.init(ruleID: (ruleID.isEmpty ? nil : ruleID),
configurationFile: configurationFile,
onlyEnabledRules: onlyEnabledRules,
onlyDisabledRules: onlyDisabledRules)
}}}
}
static func evaluate(_ mode: CommandMode) -> Result<RulesOptions, CommandantError<CommandantError<()>>> {
return create
<*> mode <| configOption
<*> mode <| Argument(defaultValue: "",
usage: "the rule identifier to display description for")
<*> mode <| Switch(flag: "e",
key: "enabled",
usage: "only display enabled rules")
<*> mode <| Switch(flag: "d",
key: "disabled",
usage: "only display disabled rules")
}
}
// MARK: - SwiftyTextTable
extension TextTable {
init(ruleList: RuleList, configuration: Configuration) {
let columns = [
TextTableColumn(header: "identifier"),
TextTableColumn(header: "opt-in"),
TextTableColumn(header: "correctable"),
TextTableColumn(header: "enabled in your config"),
TextTableColumn(header: "kind"),
TextTableColumn(header: "analyzer"),
TextTableColumn(header: "configuration")
]
self.init(columns: columns)
let sortedRules = ruleList.list.sorted { $0.0 < $1.0 }
func truncate(_ string: String) -> String {
let stringWithNoNewlines = string.replacingOccurrences(of: "\n", with: "\\n")
let minWidth = "configuration".count - "...".count
let configurationStartColumn = 112
let truncatedEndIndex = stringWithNoNewlines.index(
stringWithNoNewlines.startIndex,
offsetBy: max(minWidth, Terminal.currentWidth() - configurationStartColumn),
limitedBy: stringWithNoNewlines.endIndex
)
if let truncatedEndIndex = truncatedEndIndex {
return stringWithNoNewlines[..<truncatedEndIndex] + "..."
}
return stringWithNoNewlines
}
for (ruleID, ruleType) in sortedRules {
let rule = ruleType.init()
let configuredRule = configuration.rules.first { rule in
guard type(of: rule).description.identifier == ruleID else {
return false
}
guard let customRules = rule as? CustomRules else {
return true
}
return !customRules.configuration.customRuleConfigurations.isEmpty
}
addRow(values: [
ruleID,
rule.isOptIn ? "yes" : "no",
(rule is CorrectableRule) ? "yes" : "no",
configuredRule != nil ? "yes" : "no",
ruleType.description.kind.rawValue,
(rule is AnalyzerRule) ? "yes" : "no",
truncate((configuredRule ?? rule).configurationDescription)
])
}
}
}
struct Terminal {
static func currentWidth() -> Int {
var size = winsize()
#if os(Linux)
_ = ioctl(CInt(STDOUT_FILENO), UInt(TIOCGWINSZ), &size)
#else
_ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)
#endif
return Int(size.ws_col)
}
}