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(
|
swift_library(
|
||||||
name = "swiftlint",
|
name = "swiftlint.library",
|
||||||
|
module_name = "swiftlint",
|
||||||
srcs = glob(["Source/swiftlint/**/*.swift"]),
|
srcs = glob(["Source/swiftlint/**/*.swift"]),
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
@ -43,6 +44,14 @@ swift_binary(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
swift_binary(
|
||||||
|
name = "swiftlint",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":swiftlint.library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
# Linting
|
# Linting
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
|
|
@ -32,6 +32,9 @@
|
||||||
[JP Simard](https://github.com/jpims)
|
[JP Simard](https://github.com/jpims)
|
||||||
[#4031](https://github.com/realm/SwiftLint/issues/4031)
|
[#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
|
* Add new `excludes_trivial_init` configuration for `missing_docs` rule
|
||||||
to exclude initializers without any parameters.
|
to exclude initializers without any parameters.
|
||||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
[Marcelo Fabri](https://github.com/marcelofabri)
|
||||||
|
|
|
@ -41,6 +41,12 @@ let package = Package(
|
||||||
"SwiftyTextTable",
|
"SwiftyTextTable",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "CLITests",
|
||||||
|
dependencies: [
|
||||||
|
"swiftlint"
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "SwiftLintFramework",
|
name: "SwiftLintFramework",
|
||||||
dependencies: frameworkDependencies
|
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 {
|
extension SwiftLint {
|
||||||
struct GenerateDocs: ParsableCommand {
|
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")
|
@Option(help: "The directory where the documentation should be saved")
|
||||||
var path = "rule_docs"
|
var path = "rule_docs"
|
||||||
|
@Option(help: "The path to a SwiftLint configuration file")
|
||||||
|
var config: String?
|
||||||
|
@OptionGroup var rulesFilterOptions: RulesFilterOptions
|
||||||
|
|
||||||
func run() throws {
|
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))
|
.write(to: URL(fileURLWithPath: path, isDirectory: true))
|
||||||
ExitHelper.successfullyExit()
|
ExitHelper.successfullyExit()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,28 +10,13 @@ import Foundation
|
||||||
import SwiftLintFramework
|
import SwiftLintFramework
|
||||||
import SwiftyTextTable
|
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 {
|
extension SwiftLint {
|
||||||
struct Rules: ParsableCommand {
|
struct Rules: ParsableCommand {
|
||||||
static let configuration = CommandConfiguration(abstract: "Display the list of rules and their identifiers")
|
static let configuration = CommandConfiguration(abstract: "Display the list of rules and their identifiers")
|
||||||
|
|
||||||
@Option(help: "The path to a SwiftLint configuration file")
|
@Option(help: "The path to a SwiftLint configuration file")
|
||||||
var config: String?
|
var config: String?
|
||||||
@Flag(exclusivity: .exclusive)
|
@OptionGroup var rulesFilterOptions: RulesFilterOptions
|
||||||
var ruleEnablement: RuleEnablementOptions?
|
|
||||||
@Flag(name: .shortAndLong, help: "Only display correctable rules")
|
|
||||||
var correctable = false
|
|
||||||
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
||||||
var verbose = false
|
var verbose = false
|
||||||
@Argument(help: "The rule identifier to display description for")
|
@Argument(help: "The rule identifier to display description for")
|
||||||
|
@ -48,35 +33,12 @@ extension SwiftLint {
|
||||||
}
|
}
|
||||||
|
|
||||||
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
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)
|
let table = TextTable(ruleList: rules, configuration: configuration, verbose: verbose)
|
||||||
print(table.render())
|
print(table.render())
|
||||||
ExitHelper.successfullyExit()
|
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"])
|
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(
|
filegroup(
|
||||||
name = "SwiftLintFrameworkTestsSources",
|
name = "SwiftLintFrameworkTestsSources",
|
||||||
srcs = glob(
|
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
|
sw_vers
|
||||||
xcodebuild -version
|
xcodebuild -version
|
||||||
displayName: Version Informations
|
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
|
displayName: xcodebuild test
|
||||||
|
|
||||||
- job: SwiftPM
|
- job: SwiftPM
|
||||||
|
|
Loading…
Reference in New Issue