mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
669 lines
30 KiB
Swift
669 lines
30 KiB
Swift
import Foundation
|
|
import TestHelpers
|
|
import Testing
|
|
|
|
@testable import SwiftLintBuiltInRules
|
|
@testable import SwiftLintFramework
|
|
|
|
// swiftlint:disable file_length
|
|
|
|
private extension Configuration {
|
|
func contains<T: Rule>(rule _: T.Type) -> Bool {
|
|
rules.contains { $0 is T }
|
|
}
|
|
}
|
|
|
|
@Suite(.rulesRegistered)
|
|
struct MultipleConfigurationsTests { // swiftlint:disable:this type_body_length
|
|
// MARK: - Rules Merging
|
|
@Test(.workingDirectory(Constants.Dir.level0))
|
|
func merge() {
|
|
let config0Merge2 = Constants.Config._0.merged(withChild: Constants.Config._2)
|
|
|
|
#expect(!Constants.Config._0.contains(rule: ForceCastRule.self))
|
|
#expect(Constants.Config._2.contains(rule: ForceCastRule.self))
|
|
#expect(!config0Merge2.contains(rule: ForceCastRule.self))
|
|
|
|
#expect(Constants.Config._0.contains(rule: TodoRule.self))
|
|
#expect(Constants.Config._2.contains(rule: TodoRule.self))
|
|
#expect(config0Merge2.contains(rule: TodoRule.self))
|
|
|
|
#expect(!Constants.Config._3.contains(rule: TodoRule.self))
|
|
#expect(!config0Merge2.merged(withChild: Constants.Config._3).contains(rule: TodoRule.self))
|
|
}
|
|
|
|
// MARK: - Merging Aspects
|
|
@Test
|
|
func warningThresholdMerging() {
|
|
func configuration(forWarningThreshold warningThreshold: Int?) -> Configuration {
|
|
Configuration(
|
|
warningThreshold: warningThreshold,
|
|
reporter: XcodeReporter.identifier
|
|
)
|
|
}
|
|
#expect(configuration(forWarningThreshold: 3)
|
|
.merged(withChild: configuration(forWarningThreshold: 2)).warningThreshold == 2)
|
|
#expect(configuration(forWarningThreshold: nil)
|
|
.merged(withChild: configuration(forWarningThreshold: 2)).warningThreshold == 2)
|
|
#expect(configuration(forWarningThreshold: 3)
|
|
.merged(withChild: configuration(forWarningThreshold: nil)).warningThreshold == 3)
|
|
#expect(configuration(forWarningThreshold: nil)
|
|
.merged(withChild: configuration(forWarningThreshold: nil)).warningThreshold == nil)
|
|
}
|
|
|
|
@Test
|
|
func onlyRulesMerging() {
|
|
let baseConfiguration = Configuration(
|
|
rulesMode: .defaultConfiguration(
|
|
disabled: [],
|
|
optIn: [
|
|
ForceTryRule.identifier,
|
|
ForceCastRule.identifier,
|
|
]
|
|
)
|
|
)
|
|
let onlyConfiguration = Configuration(rulesMode: .onlyConfiguration([TodoRule.identifier]))
|
|
#expect(baseConfiguration.contains(rule: TodoRule.self))
|
|
#expect(onlyConfiguration.rules.count == 1)
|
|
#expect(onlyConfiguration.rules.first is TodoRule)
|
|
|
|
let mergedConfiguration1 = baseConfiguration.merged(withChild: onlyConfiguration)
|
|
#expect(mergedConfiguration1.rules.count == 1)
|
|
#expect(mergedConfiguration1.rules.first is TodoRule)
|
|
|
|
// Also test the other way around
|
|
let mergedConfiguration2 = onlyConfiguration.merged(withChild: baseConfiguration)
|
|
#expect(mergedConfiguration2.rules.count == 3) // 2 opt-ins + 1 from the only rules
|
|
#expect(mergedConfiguration2.contains(rule: TodoRule.self))
|
|
#expect(mergedConfiguration2.contains(rule: ForceCastRule.self))
|
|
#expect(mergedConfiguration2.contains(rule: ForceTryRule.self))
|
|
}
|
|
|
|
@Test
|
|
func onlyRuleMerging() {
|
|
let ruleIdentifier = TodoRule.identifier
|
|
let onlyRuleConfiguration = Configuration.onlyRuleConfiguration(ruleIdentifier)
|
|
|
|
let emptyDefaultConfiguration = Configuration.emptyDefaultConfiguration()
|
|
let mergedConfiguration1 = onlyRuleConfiguration.merged(withChild: emptyDefaultConfiguration)
|
|
#expect(mergedConfiguration1.rules.count == 1)
|
|
#expect(mergedConfiguration1.rules.first is TodoRule)
|
|
|
|
let disabledDefaultConfiguration = Configuration.disabledDefaultConfiguration(ruleIdentifier)
|
|
let mergedConfiguration2 = onlyRuleConfiguration.merged(withChild: disabledDefaultConfiguration)
|
|
#expect(mergedConfiguration2.rules.isEmpty)
|
|
|
|
let enabledOnlyConfiguration = Configuration.enabledOnlyConfiguration(ForceTryRule.identifier)
|
|
let mergedConfiguration3 = onlyRuleConfiguration.merged(withChild: enabledOnlyConfiguration)
|
|
#expect(mergedConfiguration3.rules.count == 1)
|
|
#expect(mergedConfiguration3.rules.first is TodoRule)
|
|
}
|
|
|
|
@Test
|
|
func customRulesMerging() {
|
|
let mergedConfiguration = Constants.Config._0CustomRules.merged(
|
|
withChild: Constants.Config._2CustomRules,
|
|
rootDirectory: URL.cwd
|
|
)
|
|
guard let mergedCustomRules = mergedConfiguration.rules.customRules
|
|
else {
|
|
Issue.record("Custom rules are expected to be present")
|
|
return
|
|
}
|
|
#expect(
|
|
mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abc" }
|
|
)
|
|
#expect(
|
|
mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abcd" }
|
|
)
|
|
}
|
|
|
|
@Test
|
|
func mergingAllowsDisablingParentsCustomRules() {
|
|
let mergedConfiguration = Constants.Config._0CustomRules.merged(
|
|
withChild: Constants.Config._2CustomRulesDisabled,
|
|
rootDirectory: URL.cwd
|
|
)
|
|
guard let mergedCustomRules = mergedConfiguration.rules.customRules
|
|
else {
|
|
Issue.record("Custom rules are expected to be present")
|
|
return
|
|
}
|
|
#expect(!mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abc" })
|
|
#expect(mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abcd" })
|
|
}
|
|
|
|
@Test
|
|
func customRulesMergingWithOnlyRulesCase1() {
|
|
// The base configuration is in only rules mode
|
|
// The child configuration is in the default rules mode
|
|
// => all custom rules should be considered
|
|
let mergedConfiguration = Constants.Config._0CustomRulesOnly.merged(
|
|
withChild: Constants.Config._2CustomRules,
|
|
rootDirectory: URL.cwd
|
|
)
|
|
guard let mergedCustomRules = mergedConfiguration.rules.customRules
|
|
else {
|
|
Issue.record("Custom rules are expected to be present")
|
|
return
|
|
}
|
|
#expect(
|
|
mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abc" }
|
|
)
|
|
#expect(
|
|
mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abcd" }
|
|
)
|
|
}
|
|
|
|
@Test
|
|
func customRulesMergingWithOnlyRulesCase2() {
|
|
// The base configuration is in only rules mode
|
|
// The child configuration is in the only rules mode
|
|
// => only the custom rules from the child configuration should be considered
|
|
// (because custom rules from base configuration would require explicit mention as one of the `only_rules`)
|
|
let mergedConfiguration = Constants.Config._0CustomRulesOnly.merged(
|
|
withChild: Constants.Config._2CustomRulesOnly,
|
|
rootDirectory: URL.cwd
|
|
)
|
|
guard let mergedCustomRules = mergedConfiguration.rules.customRules
|
|
else {
|
|
Issue.record("Custom rules are expected to be present")
|
|
return
|
|
}
|
|
#expect(!mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abc" })
|
|
#expect(mergedCustomRules.configuration.customRuleConfigurations.contains { $0.identifier == "no_abcd" })
|
|
}
|
|
|
|
@Test
|
|
func customRulesReconfiguration() {
|
|
// Custom Rule severity gets reconfigured to "error"
|
|
let mergedConfiguration = Constants.Config._0CustomRulesOnly.merged(
|
|
withChild: Constants.Config._2CustomRulesReconfig,
|
|
rootDirectory: URL.cwd
|
|
)
|
|
guard let mergedCustomRules = mergedConfiguration.rules.customRules
|
|
else {
|
|
Issue.record("Custom rules are expected to be present")
|
|
return
|
|
}
|
|
#expect(
|
|
mergedCustomRules.configuration.customRuleConfigurations.filter { $0.identifier == "no_abc" }.count == 1
|
|
)
|
|
guard let customRule = (mergedCustomRules.configuration.customRuleConfigurations.first {
|
|
$0.identifier == "no_abc"
|
|
}) else {
|
|
Issue.record("Custom rule is expected to be present")
|
|
return
|
|
}
|
|
#expect(customRule.severityConfiguration.severity == .error)
|
|
}
|
|
|
|
// MARK: - Nested Configurations
|
|
@Test(.workingDirectory(Constants.Dir.level0))
|
|
func level0() {
|
|
#expect(
|
|
Constants.Config._0.configuration(for: SwiftLintFile(path: Constants.Swift._0)!)
|
|
== Constants.Config._0
|
|
)
|
|
}
|
|
|
|
@Test(.workingDirectory(Constants.Dir.level0))
|
|
func level1() {
|
|
#expect(
|
|
Constants.Config._0.configuration(for: SwiftLintFile(path: Constants.Swift._1)!)
|
|
== Constants.Config._0
|
|
)
|
|
}
|
|
|
|
@Test
|
|
func level2() {
|
|
let config = Constants.Config._0.configuration(for: SwiftLintFile(path: Constants.Swift._2)!)
|
|
var config2 = Constants.Config._2
|
|
config2.fileGraph = Configuration.FileGraph(rootDirectory: Constants.Dir.level2)
|
|
|
|
#expect(config == Constants.Config._0.merged(withChild: config2, rootDirectory: config.rootDirectory))
|
|
}
|
|
|
|
@Test
|
|
func level3() {
|
|
let config = Constants.Config._0.configuration(for: SwiftLintFile(path: Constants.Swift._3)!)
|
|
var config3 = Constants.Config._3
|
|
config3.fileGraph = Configuration.FileGraph(rootDirectory: Constants.Dir.level3)
|
|
|
|
#expect(config == Constants.Config._0.merged(withChild: config3, rootDirectory: config.rootDirectory))
|
|
}
|
|
|
|
@Test
|
|
func nestedConfigurationForOnePathPassedIn() {
|
|
// If a path to one or more configuration files is specified, nested configurations should be ignored
|
|
let config = Configuration(configurationFiles: [Constants.Yml._0])
|
|
#expect(config.configuration(for: SwiftLintFile(path: Constants.Swift._3)!) == config)
|
|
}
|
|
|
|
@Test
|
|
func parentConfigIsIgnoredAsNestedConfiguration() {
|
|
// If a configuration has already been used to build the main config,
|
|
// it should not again be regarded as a nested config
|
|
#expect(
|
|
Constants.Config.nested.configuration(for: SwiftLintFile(path: Constants.Swift.nestedSub)!)
|
|
== Constants.Config.nested
|
|
)
|
|
}
|
|
|
|
// MARK: - Child & Parent Configs
|
|
@Test(
|
|
.disabled(if: isRunningWithBazel),
|
|
arguments: [Constants.Dir.childConfigTest1, Constants.Dir.childConfigTest2],
|
|
)
|
|
func validChildConfig(_ path: URL) {
|
|
CurrentWorkingDirectory.$url.withValue(path) {
|
|
assertEqualExceptForFileGraph(
|
|
Configuration(configurationFiles: ["main.yml".url()]),
|
|
Configuration(configurationFiles: ["expected.yml".url()])
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test(arguments: [Constants.Dir.parentConfigTest1, Constants.Dir.parentConfigTest2])
|
|
func validParentConfig(_ path: URL) {
|
|
CurrentWorkingDirectory.$url.withValue(path) {
|
|
assertEqualExceptForFileGraph(
|
|
Configuration(configurationFiles: ["main.yml".url()]),
|
|
Configuration(configurationFiles: ["expected.yml".url()])
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
.disabled(if: isRunningWithBazel),
|
|
arguments: [Constants.Dir.childConfigTest1, Constants.Dir.childConfigTest2],
|
|
)
|
|
func commandLineChildConfigs(_ path: URL) {
|
|
CurrentWorkingDirectory.$url.withValue(path) {
|
|
assertEqualExceptForFileGraph(
|
|
Configuration(
|
|
configurationFiles: [
|
|
"main.yml".url(),
|
|
"child1.yml".url(),
|
|
"child2.yml".url(),
|
|
]
|
|
),
|
|
Configuration(configurationFiles: ["expected.yml".url()])
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: [
|
|
Constants.Dir.childConfigCycle1,
|
|
Constants.Dir.childConfigCycle2,
|
|
Constants.Dir.childConfigCycle3,
|
|
Constants.Dir.parentConfigCycle1,
|
|
Constants.Dir.parentConfigCycle2,
|
|
Constants.Dir.parentConfigCycle3,
|
|
],
|
|
)
|
|
func configCycleDetection(_ path: URL) {
|
|
CurrentWorkingDirectory.$url.withValue(path) {
|
|
// If the cycle is properly detected, the config should equal the default config.
|
|
#expect(Configuration(configurationFiles: []) == Configuration())
|
|
}
|
|
}
|
|
|
|
@Test(.workingDirectory(Constants.Dir.childConfigCycle4))
|
|
func commandLineConfigsCycleDetection() {
|
|
// If the cycle is properly detected, the config should equal the default config.
|
|
assertEqualExceptForFileGraph(
|
|
Configuration(
|
|
configurationFiles: ["main.yml".url(), "child.yml".url()],
|
|
useDefaultConfigOnFailure: true
|
|
),
|
|
Configuration()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
func parentChildOptInAndDisable() {
|
|
struct TestCase: Equatable {
|
|
let optedInInParent: Bool
|
|
let disabledInParent: Bool
|
|
let optedInInChild: Bool
|
|
let disabledInChild: Bool
|
|
let isEnabled: Bool
|
|
var message: String {
|
|
"optedInInParent = \(optedInInParent) " +
|
|
"disabledInParent = \(disabledInParent) " +
|
|
"optedInInChild = \(optedInInChild) " +
|
|
"disabledInChild = \(disabledInChild)"
|
|
}
|
|
}
|
|
let testCases: [TestCase] = [
|
|
// swiftlint:disable line_length
|
|
TestCase(optedInInParent: false, disabledInParent: false, optedInInChild: false, disabledInChild: false, isEnabled: false),
|
|
TestCase(optedInInParent: true, disabledInParent: false, optedInInChild: false, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInParent: false, disabledInParent: true, optedInInChild: false, disabledInChild: false, isEnabled: false),
|
|
TestCase(optedInInParent: true, disabledInParent: true, optedInInChild: false, disabledInChild: false, isEnabled: false),
|
|
TestCase(optedInInParent: false, disabledInParent: false, optedInInChild: true, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInParent: true, disabledInParent: false, optedInInChild: true, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInParent: false, disabledInParent: true, optedInInChild: true, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInParent: true, disabledInParent: true, optedInInChild: true, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInParent: false, disabledInParent: false, optedInInChild: false, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: true, disabledInParent: false, optedInInChild: false, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: false, disabledInParent: true, optedInInChild: false, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: true, disabledInParent: true, optedInInChild: false, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: false, disabledInParent: false, optedInInChild: true, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: true, disabledInParent: false, optedInInChild: true, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: false, disabledInParent: true, optedInInChild: true, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInParent: true, disabledInParent: true, optedInInChild: true, disabledInChild: true, isEnabled: false),
|
|
// swiftlint:enable line_length
|
|
]
|
|
#expect(testCases.unique.count == 4 * 4)
|
|
let ruleType = ImplicitReturnRule.self
|
|
#expect((ruleType as Any) is any OptInRule.Type)
|
|
let ruleIdentifier = ruleType.identifier
|
|
for testCase in testCases {
|
|
let parentConfiguration = Configuration(rulesMode: .defaultConfiguration(
|
|
disabled: testCase.disabledInParent ? [ruleIdentifier] : [],
|
|
optIn: testCase.optedInInParent ? [ruleIdentifier] : []
|
|
))
|
|
let childConfiguration = Configuration(rulesMode: .defaultConfiguration(
|
|
disabled: testCase.disabledInChild ? [ruleIdentifier] : [],
|
|
optIn: testCase.optedInInChild ? [ruleIdentifier] : []
|
|
))
|
|
let mergedConfiguration = parentConfiguration.merged(withChild: childConfiguration)
|
|
let isEnabled = mergedConfiguration.contains(rule: ruleType)
|
|
#expect(isEnabled == testCase.isEnabled, Comment(rawValue: testCase.message))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func parentChildDisableForDefaultRule() {
|
|
struct TestCase: Equatable {
|
|
let disabledInParent: Bool
|
|
let disabledInChild: Bool
|
|
let isEnabled: Bool
|
|
var message: String {
|
|
"disabledInParent = \(disabledInParent) disabledInChild = \(disabledInChild)"
|
|
}
|
|
}
|
|
let testCases: [TestCase] = [
|
|
TestCase(disabledInParent: false, disabledInChild: false, isEnabled: true),
|
|
TestCase(disabledInParent: true, disabledInChild: false, isEnabled: false),
|
|
TestCase(disabledInParent: false, disabledInChild: true, isEnabled: false),
|
|
TestCase(disabledInParent: true, disabledInChild: true, isEnabled: false),
|
|
]
|
|
#expect(testCases.unique.count == 2 * 2)
|
|
let ruleType = BlanketDisableCommandRule.self
|
|
#expect(!(ruleType is any OptInRule.Type))
|
|
let ruleIdentifier = ruleType.identifier
|
|
for testCase in testCases {
|
|
let parentConfiguration = Configuration(
|
|
rulesMode: .defaultConfiguration(disabled: testCase.disabledInParent ? [ruleIdentifier] : [], optIn: [])
|
|
)
|
|
let childConfiguration = Configuration(
|
|
rulesMode: .defaultConfiguration(disabled: testCase.disabledInChild ? [ruleIdentifier] : [], optIn: [])
|
|
)
|
|
let mergedConfiguration = parentConfiguration.merged(withChild: childConfiguration)
|
|
let isEnabled = mergedConfiguration.contains(rule: ruleType)
|
|
#expect(isEnabled == testCase.isEnabled, Comment(rawValue: testCase.message))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func parentOnlyRulesAndChildOptInAndDisabled() {
|
|
struct TestCase: Equatable {
|
|
let optedInInChild: Bool
|
|
let disabledInChild: Bool
|
|
let isEnabled: Bool
|
|
var message: String {
|
|
"optedInInChild = \(optedInInChild) disabledInChild = \(disabledInChild)"
|
|
}
|
|
}
|
|
let testCases: [TestCase] = [
|
|
TestCase(optedInInChild: false, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInChild: true, disabledInChild: false, isEnabled: true),
|
|
TestCase(optedInInChild: false, disabledInChild: true, isEnabled: false),
|
|
TestCase(optedInInChild: true, disabledInChild: true, isEnabled: false),
|
|
]
|
|
#expect(testCases.unique.count == 2 * 2)
|
|
let ruleType = ImplicitReturnRule.self
|
|
#expect((ruleType as Any) is any OptInRule.Type)
|
|
let ruleIdentifier = ruleType.identifier
|
|
let parentConfiguration = Configuration(rulesMode: .onlyConfiguration([ruleIdentifier]))
|
|
for testCase in testCases {
|
|
let childConfiguration = Configuration(rulesMode: .defaultConfiguration(
|
|
disabled: testCase.disabledInChild ? [ruleIdentifier] : [],
|
|
optIn: testCase.optedInInChild ? [ruleIdentifier] : []
|
|
))
|
|
let mergedConfiguration = parentConfiguration.merged(withChild: childConfiguration)
|
|
let isEnabled = mergedConfiguration.contains(rule: ruleType)
|
|
#expect(isEnabled == testCase.isEnabled, Comment(rawValue: testCase.message))
|
|
}
|
|
}
|
|
|
|
// MARK: Warnings about configurations for disabled rules
|
|
@Test
|
|
func defaultConfigurationDisabledRuleWarnings() {
|
|
let optInRuleType = ImplicitReturnRule.self
|
|
#expect((optInRuleType as Any) is any OptInRule.Type)
|
|
testDefaultConfigurationDisabledRuleWarnings(for: optInRuleType)
|
|
|
|
let defaultRuleType = BlockBasedKVORule.self
|
|
#expect(!((defaultRuleType as Any) is any OptInRule.Type))
|
|
testDefaultConfigurationDisabledRuleWarnings(for: defaultRuleType)
|
|
}
|
|
|
|
private func testDefaultConfigurationDisabledRuleWarnings(for ruleType: any Rule.Type) {
|
|
let ruleIdentifier = ruleType.identifier
|
|
|
|
let parentConfigurations = [
|
|
nil,
|
|
Configuration.emptyDefaultConfiguration(),
|
|
Configuration.optInDefaultConfiguration(ruleIdentifier),
|
|
Configuration.optInDisabledDefaultConfiguration(ruleIdentifier),
|
|
Configuration.disabledDefaultConfiguration(ruleIdentifier),
|
|
Configuration.emptyOnlyConfiguration(),
|
|
Configuration.enabledOnlyConfiguration(ruleIdentifier),
|
|
Configuration.allEnabledConfiguration(),
|
|
]
|
|
|
|
let configurations = [
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [], optIn: [])),
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [], optIn: [ruleIdentifier])),
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [ruleIdentifier], optIn: [ruleIdentifier])),
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [ruleIdentifier], optIn: [])),
|
|
]
|
|
|
|
for parentConfiguration in parentConfigurations {
|
|
for configuration in configurations {
|
|
testParentConfiguration(parentConfiguration, configuration: configuration, ruleType: ruleType)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func testParentConfiguration(
|
|
_ parentConfiguration: Configuration?,
|
|
configuration: Configuration,
|
|
ruleType: any Rule.Type
|
|
) {
|
|
guard case let .defaultConfiguration(disabledRules, optInRules) = configuration.rulesMode else {
|
|
Issue.record("Configuration rulesMode was not the default")
|
|
return
|
|
}
|
|
|
|
let mergedConfiguration = parentConfiguration?.merged(withChild: configuration) ?? configuration
|
|
let isEnabled = mergedConfiguration.contains(rule: ruleType)
|
|
let issue = Configuration.validateConfiguredRuleIsEnabled(
|
|
parentConfiguration: parentConfiguration,
|
|
disabledRules: disabledRules,
|
|
optInRules: optInRules,
|
|
ruleType: ruleType
|
|
)
|
|
#expect(isEnabled == (issue == nil))
|
|
guard let issue else {
|
|
return
|
|
}
|
|
let ruleIdentifier = ruleType.identifier
|
|
|
|
guard disabledRules.isEmpty, optInRules.isEmpty else {
|
|
#expect(issue == Issue.ruleDisabledInDisabledRules(ruleID: ruleIdentifier))
|
|
return
|
|
}
|
|
|
|
if parentConfiguration == nil ||
|
|
parentConfiguration == Configuration.emptyDefaultConfiguration() {
|
|
#expect(issue == Issue.ruleNotEnabledInOptInRules(ruleID: ruleIdentifier))
|
|
} else if parentConfiguration == Configuration.emptyOnlyConfiguration() {
|
|
if ruleType is any OptInRule.Type {
|
|
#expect(issue == Issue.ruleNotEnabledInOptInRules(ruleID: ruleIdentifier))
|
|
} else {
|
|
#expect(issue == Issue.ruleNotEnabledInParentOnlyRules(ruleID: ruleIdentifier))
|
|
}
|
|
} else if parentConfiguration == Configuration.optInDisabledDefaultConfiguration(ruleIdentifier) ||
|
|
parentConfiguration == Configuration.disabledDefaultConfiguration(ruleIdentifier) {
|
|
#expect(issue == Issue.ruleDisabledInParentConfiguration(ruleID: ruleIdentifier))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func onlyConfigurationDisabledRulesWarnings() {
|
|
let optInRuleType = ImplicitReturnRule.self
|
|
#expect((optInRuleType as Any) is any OptInRule.Type)
|
|
testOnlyConfigurationDisabledRulesWarnings(ruleType: optInRuleType)
|
|
|
|
let defaultRuleType = BlockBasedKVORule.self
|
|
#expect(!((defaultRuleType as Any) is any OptInRule.Type))
|
|
testOnlyConfigurationDisabledRulesWarnings(ruleType: defaultRuleType)
|
|
}
|
|
|
|
private func testOnlyConfigurationDisabledRulesWarnings(ruleType: any Rule.Type) {
|
|
let issue = Configuration.validateConfiguredRuleIsEnabled(onlyRules: [], ruleType: ruleType)
|
|
#expect(issue == Issue.ruleNotPresentInOnlyRules(ruleID: ruleType.identifier))
|
|
#expect(
|
|
Configuration.validateConfiguredRuleIsEnabled(onlyRules: [ruleType.identifier], ruleType: ruleType) == nil
|
|
)
|
|
}
|
|
|
|
// MARK: - Remote Configs
|
|
@Test(.temporaryDirectory(withFixture: Constants.Dir.remoteConfigChild))
|
|
func validRemoteChildConfig() {
|
|
let mockedNetworkResults = [
|
|
"https://www.mock.com":
|
|
"""
|
|
included:
|
|
- Test/Test1/Test/Test
|
|
- Test/Test2/Test/Test
|
|
""",
|
|
]
|
|
let remoteConfig = Configuration.FileGraph.FilePath.$mockedNetworkResults.withValue(mockedNetworkResults) {
|
|
Configuration(configurationFiles: ["main.yml".url()])
|
|
}
|
|
assertEqualExceptForFileGraph(
|
|
remoteConfig,
|
|
Configuration(configurationFiles: ["expected.yml".url()])
|
|
)
|
|
}
|
|
|
|
@Test(.temporaryDirectory(withFixture: Constants.Dir.remoteConfigParent))
|
|
func validRemoteParentConfig() {
|
|
let mockedNetworkResults = [
|
|
"https://www.mock.com":
|
|
"""
|
|
included:
|
|
- Test/Test1
|
|
- Test/Test2
|
|
|
|
excluded:
|
|
- Test/Test1/Test
|
|
- Test/Test2/Test
|
|
|
|
line_length: 80
|
|
""",
|
|
]
|
|
let remoteConfig = Configuration.FileGraph.FilePath.$mockedNetworkResults.withValue(mockedNetworkResults) {
|
|
Configuration(configurationFiles: ["main.yml".url()])
|
|
}
|
|
assertEqualExceptForFileGraph(
|
|
remoteConfig,
|
|
Configuration(configurationFiles: ["expected.yml".url()])
|
|
)
|
|
}
|
|
|
|
@Test(.temporaryDirectory(withFixture: Constants.Dir.remoteConfigLocalRef))
|
|
func remoteConfigNotAllowedToReferenceLocalConfig() {
|
|
let mockedNetworkResults = [
|
|
"https://www.mock.com":
|
|
"""
|
|
line_length: 60
|
|
|
|
child_config: child.yml
|
|
""",
|
|
]
|
|
let remoteConfig = Configuration.FileGraph.FilePath.$mockedNetworkResults.withValue(mockedNetworkResults) {
|
|
Configuration(configurationFiles: []) // Not specifying a file means the .swiftlint.yml will be used.
|
|
}
|
|
|
|
// If the remote file is not allowed to reference a local file, the config should equal the default config.
|
|
#expect(remoteConfig == Configuration())
|
|
}
|
|
|
|
@Test(.temporaryDirectory(withFixture: Constants.Dir.remoteConfigCycle))
|
|
func remoteConfigCycleDetection() {
|
|
let mockedNetworkResults = [
|
|
"https://www.mock.com":
|
|
"""
|
|
child_config: https://www.mock.com
|
|
""",
|
|
]
|
|
let remoteConfig = Configuration.FileGraph.FilePath.$mockedNetworkResults.withValue(mockedNetworkResults) {
|
|
Configuration(configurationFiles: []) // Not specifying a file means the .swiftlint.yml will be used.
|
|
}
|
|
// If the cycle is properly detected, the config should equal the default config.
|
|
#expect(remoteConfig == Configuration())
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
/// This helper function checks whether two configurations are equal except for their file graph.
|
|
/// This is needed to test a child/parent merged config against an expected config.
|
|
func assertEqualExceptForFileGraph(_ configuration1: Configuration, _ configuration2: Configuration) {
|
|
#expect(
|
|
configuration1.rulesWrapper.disabledRuleIdentifiers == configuration2.rulesWrapper.disabledRuleIdentifiers
|
|
)
|
|
#expect(
|
|
configuration1.rules.map { type(of: $0).identifier } == configuration2.rules.map { type(of: $0).identifier }
|
|
)
|
|
#expect(
|
|
Set(configuration1.rulesWrapper.allRulesWrapped.map {
|
|
$0.rule.createConfigurationDescription().oneLiner()
|
|
}) == Set(configuration2.rulesWrapper.allRulesWrapped.map {
|
|
$0.rule.createConfigurationDescription().oneLiner()
|
|
})
|
|
)
|
|
#expect(configuration1.includedPaths.map(\.path).sorted() == configuration2.includedPaths.map(\.path).sorted())
|
|
#expect(configuration1.excludedPaths.map(\.path).sorted() == configuration2.excludedPaths.map(\.path).sorted())
|
|
}
|
|
}
|
|
|
|
private extension Configuration {
|
|
static func emptyDefaultConfiguration() -> Self {
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [], optIn: []))
|
|
}
|
|
static func optInDefaultConfiguration(_ ruleIdentifier: String) -> Self {
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [], optIn: [ruleIdentifier]))
|
|
}
|
|
static func optInDisabledDefaultConfiguration(_ ruleIdentifier: String) -> Self {
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [ruleIdentifier], optIn: [ruleIdentifier]))
|
|
}
|
|
static func disabledDefaultConfiguration(_ ruleIdentifier: String) -> Self {
|
|
Configuration(rulesMode: .defaultConfiguration(disabled: [ruleIdentifier], optIn: []))
|
|
}
|
|
static func emptyOnlyConfiguration() -> Self { Configuration(rulesMode: .onlyConfiguration([])) }
|
|
static func enabledOnlyConfiguration(_ ruleIdentifier: String) -> Self {
|
|
Configuration(rulesMode: .onlyConfiguration([ruleIdentifier]))
|
|
}
|
|
static func allEnabledConfiguration() -> Self { Configuration(rulesMode: .allCommandLine)}
|
|
static func onlyRuleConfiguration(_ ruleIdentifier: String) -> Self {
|
|
Configuration(rulesMode: .onlyCommandLine([ruleIdentifier]))
|
|
}
|
|
}
|