Add ability to filter rules for `generate-docs` subcommand (#4195)
Added rules filtering options, like in the `rules` subcommand. <img width="880" alt="generate-docs--help" src="https://user-images.githubusercontent.com/7829589/189372666-2f5aba62-57a1-49dc-9155-c60f9e085984.png"> To achieve reuse with `rules` subcommand: - extracted filtering logic into `RulesFilter` (and write tests) - extracted filtering command line parameters into `RulesFilterOptions: ParsableArguments`
This commit is contained in:
parent
c70510c3ea
commit
129a55fb74
13
BUILD
13
BUILD
|
@ -31,8 +31,9 @@ swift_library(
|
|||
}),
|
||||
)
|
||||
|
||||
swift_binary(
|
||||
name = "swiftlint",
|
||||
swift_library(
|
||||
name = "swiftlint.library",
|
||||
module_name = "swiftlint",
|
||||
srcs = glob(["Source/swiftlint/**/*.swift"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
|
@ -43,6 +44,14 @@ swift_binary(
|
|||
],
|
||||
)
|
||||
|
||||
swift_binary(
|
||||
name = "swiftlint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":swiftlint.library",
|
||||
],
|
||||
)
|
||||
|
||||
# Linting
|
||||
|
||||
filegroup(
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
[JP Simard](https://github.com/jpims)
|
||||
[#4031](https://github.com/realm/SwiftLint/issues/4031)
|
||||
|
||||
* Add ability to filter rules for `generate-docs` subcommand.
|
||||
[kattouf](https://github.com/kattouf)
|
||||
|
||||
* Add new `excludes_trivial_init` configuration for `missing_docs` rule
|
||||
to exclude initializers without any parameters.
|
||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
||||
|
|
|
@ -41,6 +41,12 @@ let package = Package(
|
|||
"SwiftyTextTable",
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "CLITests",
|
||||
dependencies: [
|
||||
"swiftlint"
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "SwiftLintFramework",
|
||||
dependencies: frameworkDependencies
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
extension RulesFilter.ExcludingOptions {
|
||||
static func excludingOptions(byCommandLineOptions rulesFilterOptions: RulesFilterOptions) -> Self {
|
||||
var excludingOptions: Self = []
|
||||
|
||||
switch rulesFilterOptions.ruleEnablement {
|
||||
case .enabled:
|
||||
excludingOptions.insert(.disabled)
|
||||
case .disabled:
|
||||
excludingOptions.insert(.enabled)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
||||
if rulesFilterOptions.correctable {
|
||||
excludingOptions.insert(.uncorrectable)
|
||||
}
|
||||
|
||||
return excludingOptions
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import ArgumentParser
|
||||
|
||||
enum RuleEnablementOptions: String, EnumerableFlag {
|
||||
case enabled, disabled
|
||||
|
||||
static func name(for value: RuleEnablementOptions) -> NameSpecification {
|
||||
return .shortAndLong
|
||||
}
|
||||
|
||||
static func help(for value: RuleEnablementOptions) -> ArgumentHelp? {
|
||||
return "Only show \(value.rawValue) rules"
|
||||
}
|
||||
}
|
||||
|
||||
struct RulesFilterOptions: ParsableArguments {
|
||||
@Flag(exclusivity: .exclusive)
|
||||
var ruleEnablement: RuleEnablementOptions?
|
||||
@Flag(name: .shortAndLong, help: "Only display correctable rules")
|
||||
var correctable = false
|
||||
}
|
|
@ -4,13 +4,22 @@ import SwiftLintFramework
|
|||
|
||||
extension SwiftLint {
|
||||
struct GenerateDocs: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Generates markdown documentation for all rules")
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Generates markdown documentation for selected group of rules"
|
||||
)
|
||||
|
||||
@Option(help: "The directory where the documentation should be saved")
|
||||
var path = "rule_docs"
|
||||
@Option(help: "The path to a SwiftLint configuration file")
|
||||
var config: String?
|
||||
@OptionGroup var rulesFilterOptions: RulesFilterOptions
|
||||
|
||||
func run() throws {
|
||||
try RuleListDocumentation(primaryRuleList)
|
||||
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
||||
let rulesFilter = RulesFilter(enabledRules: configuration.rules)
|
||||
let rules = rulesFilter.getRules(excluding: .excludingOptions(byCommandLineOptions: rulesFilterOptions))
|
||||
|
||||
try RuleListDocumentation(rules)
|
||||
.write(to: URL(fileURLWithPath: path, isDirectory: true))
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
|
|
|
@ -10,28 +10,13 @@ import Foundation
|
|||
import SwiftLintFramework
|
||||
import SwiftyTextTable
|
||||
|
||||
enum RuleEnablementOptions: String, EnumerableFlag {
|
||||
case enabled, disabled
|
||||
|
||||
static func name(for value: RuleEnablementOptions) -> NameSpecification {
|
||||
return .shortAndLong
|
||||
}
|
||||
|
||||
static func help(for value: RuleEnablementOptions) -> ArgumentHelp? {
|
||||
return "Only show \(value.rawValue) rules"
|
||||
}
|
||||
}
|
||||
|
||||
extension SwiftLint {
|
||||
struct Rules: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Display the list of rules and their identifiers")
|
||||
|
||||
@Option(help: "The path to a SwiftLint configuration file")
|
||||
var config: String?
|
||||
@Flag(exclusivity: .exclusive)
|
||||
var ruleEnablement: RuleEnablementOptions?
|
||||
@Flag(name: .shortAndLong, help: "Only display correctable rules")
|
||||
var correctable = false
|
||||
@OptionGroup var rulesFilterOptions: RulesFilterOptions
|
||||
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
||||
var verbose = false
|
||||
@Argument(help: "The rule identifier to display description for")
|
||||
|
@ -48,35 +33,12 @@ extension SwiftLint {
|
|||
}
|
||||
|
||||
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
||||
let rules = ruleList(configuration: configuration)
|
||||
let rulesFilter = RulesFilter(enabledRules: configuration.rules)
|
||||
let rules = rulesFilter.getRules(excluding: .excludingOptions(byCommandLineOptions: rulesFilterOptions))
|
||||
let table = TextTable(ruleList: rules, configuration: configuration, verbose: verbose)
|
||||
print(table.render())
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
|
||||
private func ruleList(configuration: Configuration) -> RuleList {
|
||||
guard ruleEnablement != nil || correctable else {
|
||||
return primaryRuleList
|
||||
}
|
||||
|
||||
let filtered: [Rule.Type] = primaryRuleList.list.compactMap { ruleID, ruleType in
|
||||
let configuredRule = configuration.rules.first { rule in
|
||||
return type(of: rule).description.identifier == ruleID
|
||||
}
|
||||
|
||||
if ruleEnablement == .enabled && configuredRule == nil {
|
||||
return nil
|
||||
} else if ruleEnablement == .disabled && configuredRule != nil {
|
||||
return nil
|
||||
} else if correctable && !(configuredRule is CorrectableRule) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ruleType
|
||||
}
|
||||
|
||||
return RuleList(rules: filtered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import SwiftLintFramework
|
||||
|
||||
extension RulesFilter {
|
||||
struct ExcludingOptions: OptionSet {
|
||||
let rawValue: Int
|
||||
|
||||
static let enabled = ExcludingOptions(rawValue: 1 << 0)
|
||||
static let disabled = ExcludingOptions(rawValue: 1 << 1)
|
||||
static let uncorrectable = ExcludingOptions(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)
|
||||
}
|
||||
}
|
16
Tests/BUILD
16
Tests/BUILD
|
@ -2,6 +2,22 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library", "swift_test")
|
|||
|
||||
exports_files(["BUILD"])
|
||||
|
||||
swift_library(
|
||||
name = "CLITests.library",
|
||||
testonly = True,
|
||||
srcs = glob(["CLITests/**/*.swift"]),
|
||||
module_name = "CLITests",
|
||||
deps = [
|
||||
"//:swiftlint.library",
|
||||
],
|
||||
)
|
||||
|
||||
swift_test(
|
||||
name = "CLITests",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":CLITests.library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "SwiftLintFrameworkTestsSources",
|
||||
srcs = glob(
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
@testable import swiftlint
|
||||
import SwiftLintFramework
|
||||
import XCTest
|
||||
|
||||
final class RulesFilterTests: XCTestCase {
|
||||
func testRulesFilterExcludesEnabledRules() {
|
||||
let allRules = RuleList(
|
||||
rules: [
|
||||
RuleMock1.self,
|
||||
RuleMock2.self,
|
||||
CorrectableRuleMock.self
|
||||
]
|
||||
)
|
||||
let enabledRules: [Rule] = [
|
||||
RuleMock1(),
|
||||
CorrectableRuleMock()
|
||||
]
|
||||
let rulesFilter = RulesFilter(
|
||||
allRules: allRules,
|
||||
enabledRules: enabledRules
|
||||
)
|
||||
|
||||
let filteredRules = rulesFilter.getRules(excluding: [.enabled])
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(filteredRules.list.keys),
|
||||
Set([RuleMock2.description.identifier])
|
||||
)
|
||||
}
|
||||
|
||||
func testRulesFilterExcludesDisabledRules() {
|
||||
let allRules = RuleList(
|
||||
rules: [
|
||||
RuleMock1.self,
|
||||
RuleMock2.self,
|
||||
CorrectableRuleMock.self
|
||||
]
|
||||
)
|
||||
let enabledRules: [Rule] = [
|
||||
RuleMock1(),
|
||||
CorrectableRuleMock()
|
||||
]
|
||||
let rulesFilter = RulesFilter(
|
||||
allRules: allRules,
|
||||
enabledRules: enabledRules
|
||||
)
|
||||
|
||||
let filteredRules = rulesFilter.getRules(excluding: [.disabled])
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(filteredRules.list.keys),
|
||||
Set([RuleMock1.description.identifier, CorrectableRuleMock.description.identifier])
|
||||
)
|
||||
}
|
||||
|
||||
func testRulesFilterExcludesUncorrectableRules() {
|
||||
let allRules = RuleList(
|
||||
rules: [
|
||||
RuleMock1.self,
|
||||
RuleMock2.self,
|
||||
CorrectableRuleMock.self
|
||||
]
|
||||
)
|
||||
let enabledRules: [Rule] = [
|
||||
RuleMock1(),
|
||||
CorrectableRuleMock()
|
||||
]
|
||||
let rulesFilter = RulesFilter(
|
||||
allRules: allRules,
|
||||
enabledRules: enabledRules
|
||||
)
|
||||
|
||||
let filteredRules = rulesFilter.getRules(excluding: [.uncorrectable])
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(filteredRules.list.keys),
|
||||
Set([CorrectableRuleMock.description.identifier])
|
||||
)
|
||||
}
|
||||
|
||||
func testRulesFilterExcludesUncorrectableDisabledRules() {
|
||||
let allRules = RuleList(
|
||||
rules: [
|
||||
RuleMock1.self,
|
||||
RuleMock2.self,
|
||||
CorrectableRuleMock.self
|
||||
]
|
||||
)
|
||||
let enabledRules: [Rule] = [
|
||||
RuleMock1(),
|
||||
CorrectableRuleMock()
|
||||
]
|
||||
let rulesFilter = RulesFilter(
|
||||
allRules: allRules,
|
||||
enabledRules: enabledRules
|
||||
)
|
||||
|
||||
let filteredRules = rulesFilter.getRules(excluding: [.disabled, .uncorrectable])
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(filteredRules.list.keys),
|
||||
Set([CorrectableRuleMock.description.identifier])
|
||||
)
|
||||
}
|
||||
|
||||
func testRulesFilterExcludesUncorrectableEnabledRules() {
|
||||
let allRules = RuleList(
|
||||
rules: [
|
||||
RuleMock1.self,
|
||||
RuleMock2.self,
|
||||
CorrectableRuleMock.self
|
||||
]
|
||||
)
|
||||
let enabledRules: [Rule] = [
|
||||
RuleMock1()
|
||||
]
|
||||
let rulesFilter = RulesFilter(
|
||||
allRules: allRules,
|
||||
enabledRules: enabledRules
|
||||
)
|
||||
|
||||
let filteredRules = rulesFilter.getRules(excluding: [.enabled, .uncorrectable])
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(filteredRules.list.keys),
|
||||
Set([CorrectableRuleMock.description.identifier])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
private struct RuleMock1: Rule {
|
||||
var configurationDescription: String { return "N/A" }
|
||||
static let description = RuleDescription(identifier: "RuleMock1", name: "",
|
||||
description: "", kind: .style)
|
||||
|
||||
init() {}
|
||||
init(configuration: Any) throws { self.init() }
|
||||
|
||||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private struct RuleMock2: Rule {
|
||||
var configurationDescription: String { return "N/A" }
|
||||
static let description = RuleDescription(identifier: "RuleMock2", name: "",
|
||||
description: "", kind: .style)
|
||||
|
||||
init() {}
|
||||
init(configuration: Any) throws { self.init() }
|
||||
|
||||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private struct CorrectableRuleMock: CorrectableRule {
|
||||
var configurationDescription: String { return "N/A" }
|
||||
static let description = RuleDescription(identifier: "CorrectableRuleMock", name: "",
|
||||
description: "", kind: .style)
|
||||
|
||||
init() {}
|
||||
init(configuration: Any) throws { self.init() }
|
||||
|
||||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
return []
|
||||
}
|
||||
|
||||
func correct(file: SwiftLintFile) -> [Correction] {
|
||||
[]
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
sw_vers
|
||||
xcodebuild -version
|
||||
displayName: Version Informations
|
||||
- script: xcodebuild -scheme swiftlint test -destination "platform=macOS" OTHER_SWIFT_FLAGS="-D DISABLE_FOCUSED_EXAMPLES"
|
||||
- script: xcodebuild -scheme swiftlint test -destination "platform=macOS" OTHER_SWIFT_FLAGS="\$(inherited) -D DISABLE_FOCUSED_EXAMPLES"
|
||||
displayName: xcodebuild test
|
||||
|
||||
- job: SwiftPM
|
||||
|
|
Loading…
Reference in New Issue