From 06e4e3cc0779f04154bc790ca5ce5c0e340e6a3e Mon Sep 17 00:00:00 2001 From: Martin Redington Date: Sat, 7 Sep 2024 22:15:21 +0100 Subject: [PATCH] Fix `superfluous_disable_command` for `custom_rules` (#5670) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danny Mösch --- CHANGELOG.md | 7 +- .../Extensions/Configuration+RulesMode.swift | 3 +- .../Configuration+RulesWrapper.swift | 5 +- Source/SwiftLintCore/Models/Linter.swift | 77 ++-- Source/SwiftLintCore/Models/Region.swift | 4 + Source/SwiftLintCore/Protocols/Rule.swift | 34 ++ Source/SwiftLintCore/Rules/CustomRules.swift | 36 +- .../Rules/SuperfluousDisableCommandRule.swift | 4 +- .../CustomRulesTests.swift | 331 +++++++++++++++--- 9 files changed, 409 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65d7db8b2..3dd8e064e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,12 @@ #### Bug Fixes -* None. +* `superfluous_disable_command` violations are now triggered for + custom rules. + [Marcelo Fabri](https://github.com/marcelofabri) + [Martin Redington](https://github.com/mildm8nnered) + [SimplyDanny](https://github.com/SimplyDanny) + [#4754](https://github.com/realm/SwiftLint/issues/4754) ## 0.56.2: Heat Pump Dryer diff --git a/Source/SwiftLintCore/Extensions/Configuration+RulesMode.swift b/Source/SwiftLintCore/Extensions/Configuration+RulesMode.swift index f7d0e6b7a..e03cadd0a 100644 --- a/Source/SwiftLintCore/Extensions/Configuration+RulesMode.swift +++ b/Source/SwiftLintCore/Extensions/Configuration+RulesMode.swift @@ -112,8 +112,7 @@ public extension Configuration { switch self { case let .only(onlyRules) where onlyRules.contains { $0 == CustomRules.description.identifier }: let customRulesRule = (allRulesWrapped.first { $0.rule is CustomRules })?.rule as? CustomRules - let customRuleIdentifiers = customRulesRule?.configuration.customRuleConfigurations.map(\.identifier) - return .only(onlyRules.union(Set(customRuleIdentifiers ?? []))) + return .only(onlyRules.union(Set(customRulesRule?.customRuleIdentifiers ?? []))) default: return self diff --git a/Source/SwiftLintCore/Extensions/Configuration+RulesWrapper.swift b/Source/SwiftLintCore/Extensions/Configuration+RulesWrapper.swift index 18cdfc42b..e06a692a6 100644 --- a/Source/SwiftLintCore/Extensions/Configuration+RulesWrapper.swift +++ b/Source/SwiftLintCore/Extensions/Configuration+RulesWrapper.swift @@ -13,8 +13,7 @@ internal extension Configuration { private var validRuleIdentifiers: Set { let regularRuleIdentifiers = allRulesWrapped.map { type(of: $0.rule).description.identifier } let configurationCustomRulesIdentifiers = - (allRulesWrapped.first { $0.rule is CustomRules }?.rule as? CustomRules)? - .configuration.customRuleConfigurations.map(\.identifier) ?? [] + (allRulesWrapped.first { $0.rule is CustomRules }?.rule as? CustomRules)?.customRuleIdentifiers ?? [] return Set(regularRuleIdentifiers + configurationCustomRulesIdentifiers) } @@ -247,7 +246,7 @@ internal extension Configuration { as? CustomRules { onlyRules = onlyRules.union( Set( - childCustomRulesRule.configuration.customRuleConfigurations.map(\.identifier) + childCustomRulesRule.customRuleIdentifiers ) ) } diff --git a/Source/SwiftLintCore/Models/Linter.swift b/Source/SwiftLintCore/Models/Linter.swift index c18ce0ad1..23bfad7d5 100644 --- a/Source/SwiftLintCore/Models/Linter.swift +++ b/Source/SwiftLintCore/Models/Linter.swift @@ -16,47 +16,56 @@ private struct LintResult { } private extension Rule { - static func superfluousDisableCommandViolations(regions: [Region], - superfluousDisableCommandRule: SuperfluousDisableCommandRule?, - allViolations: [StyleViolation]) -> [StyleViolation] { + func superfluousDisableCommandViolations(regions: [Region], + superfluousDisableCommandRule: SuperfluousDisableCommandRule?, + allViolations: [StyleViolation]) -> [StyleViolation] { guard regions.isNotEmpty, let superfluousDisableCommandRule else { return [] } - let regionsDisablingCurrentRule = regions.filter { region in - region.isRuleDisabled(self.init()) - } let regionsDisablingSuperfluousDisableRule = regions.filter { region in region.isRuleDisabled(superfluousDisableCommandRule) } - return regionsDisablingCurrentRule.compactMap { region -> StyleViolation? in - let isSuperfluousRuleDisabled = regionsDisablingSuperfluousDisableRule.contains { - $0.contains(region.start) + var superfluousDisableCommandViolations = [StyleViolation]() + for region in regions { + if regionsDisablingSuperfluousDisableRule.contains(where: { $0.contains(region.start) }) { + continue } - - guard !isSuperfluousRuleDisabled else { - return nil + let sortedDisabledIdentifiers = region.disabledRuleIdentifiers.sorted { + $0.stringRepresentation < $1.stringRepresentation } - - let noViolationsInDisabledRegion = !allViolations.contains { violation in - region.contains(violation.location) + commandIDsLoop: for disabledIdentifier in sortedDisabledIdentifiers { + guard !isEnabled(in: region, for: disabledIdentifier.stringRepresentation) else { + continue + } + var disableCommandValid = false + for violation in allViolations where region.contains(violation.location) { + if canBeDisabled(violation: violation, by: disabledIdentifier) { + disableCommandValid = true + continue commandIDsLoop + } + } + if !disableCommandValid { + let reason = superfluousDisableCommandRule.reason( + forRuleIdentifier: disabledIdentifier.stringRepresentation + ) + superfluousDisableCommandViolations.append( + StyleViolation( + ruleDescription: type(of: superfluousDisableCommandRule).description, + severity: superfluousDisableCommandRule.configuration.severity, + location: region.start, + reason: reason + ) + ) + } } - guard noViolationsInDisabledRegion else { - return nil - } - - return StyleViolation( - ruleDescription: type(of: superfluousDisableCommandRule).description, - severity: superfluousDisableCommandRule.configuration.severity, - location: region.start, - reason: superfluousDisableCommandRule.reason(for: self) - ) } + return superfluousDisableCommandViolations } // As we need the configuration to get custom identifiers. - // swiftlint:disable:next function_parameter_count + // swiftlint:disable:next function_parameter_count function_body_length func lint(file: SwiftLintFile, regions: [Region], benchmark: Bool, @@ -93,16 +102,26 @@ private extension Rule { let (disabledViolationsAndRegions, enabledViolationsAndRegions) = violations.map { violation in (violation, regions.first { $0.contains(violation.location) }) - }.partitioned { _, region in - region?.isRuleEnabled(self) ?? true + }.partitioned { violation, region in + if let region { + return isEnabled(in: region, for: violation.ruleIdentifier) + } + return true } + let customRulesIDs: [String] = { + guard let customRules = self as? CustomRules else { + return [] + } + return customRules.customRuleIdentifiers + }() let ruleIDs = Self.description.allIdentifiers + + customRulesIDs + (superfluousDisableCommandRule.map({ type(of: $0) })?.description.allIdentifiers ?? []) + [RuleIdentifier.all.stringRepresentation] let ruleIdentifiers = Set(ruleIDs.map { RuleIdentifier($0) }) - let superfluousDisableCommandViolations = Self.superfluousDisableCommandViolations( + let superfluousDisableCommandViolations = superfluousDisableCommandViolations( regions: regions.count > 1 ? file.regions(restrictingRuleIdentifiers: ruleIdentifiers) : regions, superfluousDisableCommandRule: superfluousDisableCommandRule, allViolations: violations diff --git a/Source/SwiftLintCore/Models/Region.swift b/Source/SwiftLintCore/Models/Region.swift index ae2df0fcb..9c6483fbd 100644 --- a/Source/SwiftLintCore/Models/Region.swift +++ b/Source/SwiftLintCore/Models/Region.swift @@ -44,6 +44,10 @@ public struct Region: Equatable { /// /// - parameter rule: The rule whose status should be determined. /// + /// - note: For CustomRules, this will only return true if the `custom_rules` identifier + /// is used with the `swiftlint` disable command, but this method is never + /// called for CustomRules. + /// /// - returns: True if the specified rule is disabled in this region. public func isRuleDisabled(_ rule: some Rule) -> Bool { areRulesDisabled(ruleIDs: type(of: rule).description.allIdentifiers) diff --git a/Source/SwiftLintCore/Protocols/Rule.swift b/Source/SwiftLintCore/Protocols/Rule.swift index d04f234e5..5e0214b67 100644 --- a/Source/SwiftLintCore/Protocols/Rule.swift +++ b/Source/SwiftLintCore/Protocols/Rule.swift @@ -70,6 +70,26 @@ public protocol Rule { /// /// - returns: All style violations to the rule's expectations. func validate(file: SwiftLintFile, using storage: RuleStorage, compilerArguments: [String]) -> [StyleViolation] + + /// Checks if a style violation can be disabled by a command specifying a rule ID. Only the rule can claim that for + /// sure since it knows all the possible identifiers. + /// + /// - Parameters: + /// - violation: A style violation. + /// - ruleID: The name of a rule as used in a disable command. + /// + /// - Returns: A boolean value indicating whether the violation can be disabled by the given ID. + func canBeDisabled(violation: StyleViolation, by ruleID: RuleIdentifier) -> Bool + + /// Checks if a the rule is enabled in a given region. A specific rule ID can be provided in case a rule supports + /// more than one identifier. + /// + /// - Parameters: + /// - region: The region to check. + /// - ruleID: Rule identifier deviating from the default rule's name. + /// + /// - Returns: A boolean value indicating whether the rule is enabled in the given region. + func isEnabled(in region: Region, for ruleID: String) -> Bool } public extension Rule { @@ -110,6 +130,20 @@ public extension Rule { func createConfigurationDescription(exclusiveOptions: Set = []) -> RuleConfigurationDescription { RuleConfigurationDescription.from(configuration: configuration, exclusiveOptions: exclusiveOptions) } + + func canBeDisabled(violation: StyleViolation, by ruleID: RuleIdentifier) -> Bool { + switch ruleID { + case .all: + true + case let .single(identifier: id): + Self.description.allIdentifiers.contains(id) + && Self.description.allIdentifiers.contains(violation.ruleIdentifier) + } + } + + func isEnabled(in region: Region, for ruleID: String) -> Bool { + !Self.description.allIdentifiers.contains(ruleID) || region.isRuleEnabled(self) + } } public extension Rule { diff --git a/Source/SwiftLintCore/Rules/CustomRules.swift b/Source/SwiftLintCore/Rules/CustomRules.swift index c43e191f5..e523c757a 100644 --- a/Source/SwiftLintCore/Rules/CustomRules.swift +++ b/Source/SwiftLintCore/Rules/CustomRules.swift @@ -41,6 +41,10 @@ struct CustomRules: Rule, CacheDescriptionProvider { configuration.cacheDescription } + var customRuleIdentifiers: [String] { + configuration.customRuleConfigurations.map(\.identifier) + } + static let description = RuleDescription( identifier: "custom_rules", name: "Custom Rules", @@ -79,19 +83,29 @@ struct CustomRules: Rule, CacheDescriptionProvider { severity: configuration.severity, location: Location(file: file, characterOffset: $0.location), reason: configuration.message) - }).filter { violation in - guard let region = file.regions().first(where: { $0.contains(violation.location) }) else { - return true - } - - return !region.isRuleDisabled(customRuleIdentifier: configuration.identifier) - } + }) } } -} -private extension Region { - func isRuleDisabled(customRuleIdentifier: String) -> Bool { - disabledRuleIdentifiers.contains(RuleIdentifier(customRuleIdentifier)) + func canBeDisabled(violation: StyleViolation, by ruleID: RuleIdentifier) -> Bool { + switch ruleID { + case let .single(identifier: id): + id == Self.description.identifier + ? customRuleIdentifiers.contains(violation.ruleIdentifier) + : customRuleIdentifiers.contains(id) && violation.ruleIdentifier == id + default: + (self as any Rule).canBeDisabled(violation: violation, by: ruleID) + } + } + + func isEnabled(in region: Region, for ruleID: String) -> Bool { + if !Self.description.allIdentifiers.contains(ruleID), + !customRuleIdentifiers.contains(ruleID), + Self.description.identifier != ruleID { + return true + } + return !region.disabledRuleIdentifiers.contains(RuleIdentifier(Self.description.identifier)) + && !region.disabledRuleIdentifiers.contains(RuleIdentifier(ruleID)) + && !region.disabledRuleIdentifiers.contains(.all) } } diff --git a/Source/SwiftLintCore/Rules/SuperfluousDisableCommandRule.swift b/Source/SwiftLintCore/Rules/SuperfluousDisableCommandRule.swift index 0551575d8..fab3ef0b5 100644 --- a/Source/SwiftLintCore/Rules/SuperfluousDisableCommandRule.swift +++ b/Source/SwiftLintCore/Rules/SuperfluousDisableCommandRule.swift @@ -34,9 +34,9 @@ package struct SuperfluousDisableCommandRule: SourceKitFreeRule { [] } - func reason(for rule: (some Rule).Type) -> String { + func reason(forRuleIdentifier ruleIdentifier: String) -> String { """ - SwiftLint rule '\(rule.description.identifier)' did not trigger a violation in the disabled region; \ + SwiftLint rule '\(ruleIdentifier)' did not trigger a violation in the disabled region; \ remove the disable command """ } diff --git a/Tests/SwiftLintFrameworkTests/CustomRulesTests.swift b/Tests/SwiftLintFrameworkTests/CustomRulesTests.swift index 448886f60..333782204 100644 --- a/Tests/SwiftLintFrameworkTests/CustomRulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/CustomRulesTests.swift @@ -2,8 +2,13 @@ import SourceKittenFramework @testable import SwiftLintCore import XCTest +// swiftlint:disable file_length +// swiftlint:disable:next type_body_length final class CustomRulesTests: SwiftLintTestCase { - typealias Configuration = RegexConfiguration + private typealias Configuration = RegexConfiguration + + private var testFile: SwiftLintFile { SwiftLintFile(path: "\(testResourcesPath)/test.txt")! } + func testCustomRuleConfigurationSetsCorrectlyWithMatchKinds() { let configDict = [ "my_custom_rule": [ @@ -122,10 +127,16 @@ final class CustomRulesTests: SwiftLintTestCase { ) } - func testLocalDisableCustomRule() { - let (_, customRules) = getCustomRules() - let file = SwiftLintFile(contents: "//swiftlint:disable custom \n// file with a pattern") - XCTAssertEqual(customRules.validate(file: file), []) + func testLocalDisableCustomRule() throws { + let customRules: [String: Any] = [ + "custom": [ + "regex": "pattern", + "match_kinds": "comment", + ], + ] + let example = Example("//swiftlint:disable custom \n// file with a pattern") + let violations = try violations(forExample: example, customRules: customRules) + XCTAssertTrue(violations.isEmpty) } func testLocalDisableCustomRuleWithMultipleRules() { @@ -147,7 +158,7 @@ final class CustomRulesTests: SwiftLintTestCase { func testCustomRulesIncludedDefault() { // Violation detected when included is omitted. let (_, customRules) = getCustomRules() - let violations = customRules.validate(file: getTestTextFile()) + let violations = customRules.validate(file: testFile) XCTAssertEqual(violations.count, 1) } @@ -158,8 +169,8 @@ final class CustomRulesTests: SwiftLintTestCase { customRuleConfiguration.customRuleConfigurations = [regexConfig] customRules.configuration = customRuleConfiguration - let violations = customRules.validate(file: getTestTextFile()) - XCTAssertEqual(violations.count, 0) + let violations = customRules.validate(file: testFile) + XCTAssertTrue(violations.isEmpty) } func testCustomRulesExcludedExcludesFile() { @@ -169,8 +180,8 @@ final class CustomRulesTests: SwiftLintTestCase { customRuleConfiguration.customRuleConfigurations = [regexConfig] customRules.configuration = customRuleConfiguration - let violations = customRules.validate(file: getTestTextFile()) - XCTAssertEqual(violations.count, 0) + let violations = customRules.validate(file: testFile) + XCTAssertTrue(violations.isEmpty) } func testCustomRulesExcludedArrayExcludesFile() { @@ -180,8 +191,8 @@ final class CustomRulesTests: SwiftLintTestCase { customRuleConfiguration.customRuleConfigurations = [regexConfig] customRules.configuration = customRuleConfiguration - let violations = customRules.validate(file: getTestTextFile()) - XCTAssertEqual(violations.count, 0) + let violations = customRules.validate(file: testFile) + XCTAssertTrue(violations.isEmpty) } func testCustomRulesCaptureGroup() { @@ -189,12 +200,235 @@ final class CustomRulesTests: SwiftLintTestCase { "regex": #"\ba\s+(\w+)"#, "capture_group": 1, ]) - let violations = customRules.validate(file: getTestTextFile()) + let violations = customRules.validate(file: testFile) XCTAssertEqual(violations.count, 1) XCTAssertEqual(violations[0].location.line, 2) XCTAssertEqual(violations[0].location.character, 6) } + // MARK: - superfluous_disable_command support + + func testCustomRulesTriggersSuperfluousDisableCommand() throws { + let customRuleIdentifier = "forbidden" + let customRules: [String: Any] = [ + customRuleIdentifier: [ + "regex": "FORBIDDEN", + ], + ] + let example = Example(""" + // swiftlint:disable:next custom_rules + let ALLOWED = 2 + """) + + let violations = try violations(forExample: example, customRules: customRules) + XCTAssertEqual(violations.count, 1) + XCTAssertTrue(violations[0].isSuperfluousDisableCommandViolation(for: "custom_rules")) + } + + func testSpecificCustomRuleTriggersSuperfluousDisableCommand() throws { + let customRuleIdentifier = "forbidden" + let customRules: [String: Any] = [ + customRuleIdentifier: [ + "regex": "FORBIDDEN", + ], + ] + + let example = Example(""" + // swiftlint:disable:next \(customRuleIdentifier) + let ALLOWED = 2 + """) + + let violations = try violations(forExample: example, customRules: customRules) + XCTAssertEqual(violations.count, 1) + XCTAssertTrue(violations[0].isSuperfluousDisableCommandViolation(for: customRuleIdentifier)) + } + + func testSpecificAndCustomRulesTriggersSuperfluousDisableCommand() throws { + let customRuleIdentifier = "forbidden" + let customRules: [String: Any] = [ + customRuleIdentifier: [ + "regex": "FORBIDDEN", + ], + ] + + let example = Example(""" + // swiftlint:disable:next custom_rules \(customRuleIdentifier) + let ALLOWED = 2 + """) + + let violations = try violations(forExample: example, customRules: customRules) + + XCTAssertEqual(violations.count, 2) + XCTAssertTrue(violations[0].isSuperfluousDisableCommandViolation(for: "custom_rules")) + XCTAssertTrue(violations[1].isSuperfluousDisableCommandViolation(for: "\(customRuleIdentifier)")) + } + + func testCustomRulesViolationAndViolationOfSuperfluousDisableCommand() throws { + let customRuleIdentifier = "forbidden" + let customRules: [String: Any] = [ + customRuleIdentifier: [ + "regex": "FORBIDDEN", + ], + ] + + let example = Example(""" + let FORBIDDEN = 1 + // swiftlint:disable:next \(customRuleIdentifier) + let ALLOWED = 2 + """) + + let violations = try violations(forExample: example, customRules: customRules) + + XCTAssertEqual(violations.count, 2) + XCTAssertEqual(violations[0].ruleIdentifier, customRuleIdentifier) + XCTAssertTrue(violations[1].isSuperfluousDisableCommandViolation(for: customRuleIdentifier)) + } + + func testDisablingCustomRulesDoesNotTriggerSuperfluousDisableCommand() throws { + let customRules: [String: Any] = [ + "forbidden": [ + "regex": "FORBIDDEN", + ], + ] + + let example = Example(""" + // swiftlint:disable:next custom_rules + let FORBIDDEN = 1 + """) + + XCTAssertTrue(try violations(forExample: example, customRules: customRules).isEmpty) + } + + func testMultipleSpecificCustomRulesTriggersSuperfluousDisableCommand() throws { + let customRules = [ + "forbidden": [ + "regex": "FORBIDDEN", + ], + "forbidden2": [ + "regex": "FORBIDDEN2", + ], + ] + let example = Example(""" + // swiftlint:disable:next forbidden forbidden2 + let ALLOWED = 2 + """) + + let violations = try self.violations(forExample: example, customRules: customRules) + XCTAssertEqual(violations.count, 2) + XCTAssertTrue(violations[0].isSuperfluousDisableCommandViolation(for: "forbidden")) + XCTAssertTrue(violations[1].isSuperfluousDisableCommandViolation(for: "forbidden2")) + } + + func testUnviolatedSpecificCustomRulesTriggersSuperfluousDisableCommand() throws { + let customRules = [ + "forbidden": [ + "regex": "FORBIDDEN", + ], + "forbidden2": [ + "regex": "FORBIDDEN2", + ], + ] + let example = Example(""" + // swiftlint:disable:next forbidden forbidden2 + let FORBIDDEN = 1 + """) + + let violations = try self.violations(forExample: example, customRules: customRules) + XCTAssertEqual(violations.count, 1) + XCTAssertTrue(violations[0].isSuperfluousDisableCommandViolation(for: "forbidden2")) + } + + func testViolatedSpecificAndGeneralCustomRulesTriggersSuperfluousDisableCommand() throws { + let customRules = [ + "forbidden": [ + "regex": "FORBIDDEN", + ], + "forbidden2": [ + "regex": "FORBIDDEN2", + ], + ] + let example = Example(""" + // swiftlint:disable:next forbidden forbidden2 custom_rules + let FORBIDDEN = 1 + """) + + let violations = try self.violations(forExample: example, customRules: customRules) + XCTAssertEqual(violations.count, 1) + XCTAssertTrue(violations[0].isSuperfluousDisableCommandViolation(for: "forbidden2")) + } + + func testSuperfluousDisableCommandWithMultipleCustomRules() throws { + let customRules: [String: Any] = [ + "custom1": [ + "regex": "pattern", + "match_kinds": "comment", + ], + "custom2": [ + "regex": "10", + "match_kinds": "number", + ], + "custom3": [ + "regex": "100", + "match_kinds": "number", + ], + ] + + let example = Example( + """ + // swiftlint:disable custom1 custom3 + return 10 + """ + ) + + let violations = try violations(forExample: example, customRules: customRules) + + XCTAssertEqual(violations.count, 3) + XCTAssertEqual(violations[0].ruleIdentifier, "custom2") + XCTAssertTrue(violations[1].isSuperfluousDisableCommandViolation(for: "custom1")) + XCTAssertTrue(violations[2].isSuperfluousDisableCommandViolation(for: "custom3")) + } + + func testViolatedCustomRuleDoesNotTriggerSuperfluousDisableCommand() throws { + let customRules: [String: Any] = [ + "dont_print": [ + "regex": "print\\(" + ], + ] + let example = Example(""" + // swiftlint:disable:next dont_print + print("Hello, world") + """) + XCTAssertTrue(try violations(forExample: example, customRules: customRules).isEmpty) + } + + func testDisableAllDoesNotTriggerSuperfluousDisableCommand() throws { + let customRules: [String: Any] = [ + "dont_print": [ + "regex": "print\\(" + ], + ] + let example = Example(""" + // swiftlint:disable:next all + print("Hello, world") + """) + XCTAssertTrue(try violations(forExample: example, customRules: customRules).isEmpty) + } + + func testDisableAllAndDisableSpecificCustomRuleDoesNotTriggerSuperfluousDisableCommand() throws { + let customRules: [String: Any] = [ + "dont_print": [ + "regex": "print\\(" + ], + ] + let example = Example(""" + // swiftlint:disable:next all dont_print + print("Hello, world") + """) + XCTAssertTrue(try violations(forExample: example, customRules: customRules).isEmpty) + } + + // MARK: - Private + private func getCustomRules(_ extraConfig: [String: Any] = [:]) -> (Configuration, CustomRules) { var config: [String: Any] = [ "regex": "pattern", @@ -202,18 +436,8 @@ final class CustomRulesTests: SwiftLintTestCase { ] extraConfig.forEach { config[$0] = $1 } - var regexConfig = RegexConfiguration(identifier: "custom") - do { - try regexConfig.apply(configuration: config) - } catch { - XCTFail("Failed regex config") - } - - var customRuleConfiguration = CustomRulesConfiguration() - customRuleConfiguration.customRuleConfigurations = [regexConfig] - - var customRules = CustomRules() - customRules.configuration = customRuleConfiguration + let regexConfig = configuration(withIdentifier: "custom", configurationDict: config) + let customRules = customRules(withConfigurations: [regexConfig]) return (regexConfig, customRules) } @@ -223,34 +447,53 @@ final class CustomRulesTests: SwiftLintTestCase { "match_kinds": "comment", ] - var regexConfig1 = Configuration(identifier: "custom1") - do { - try regexConfig1.apply(configuration: config1) - } catch { - XCTFail("Failed regex config") - } + let regexConfig1 = configuration(withIdentifier: "custom1", configurationDict: config1) let config2 = [ "regex": "something", "match_kinds": "comment", ] - var regexConfig2 = Configuration(identifier: "custom2") - do { - try regexConfig2.apply(configuration: config2) - } catch { - XCTFail("Failed regex config") - } + let regexConfig2 = configuration(withIdentifier: "custom2", configurationDict: config2) - var customRuleConfiguration = CustomRulesConfiguration() - customRuleConfiguration.customRuleConfigurations = [regexConfig1, regexConfig2] - - var customRules = CustomRules() - customRules.configuration = customRuleConfiguration + let customRules = customRules(withConfigurations: [regexConfig1, regexConfig2]) return ((regexConfig1, regexConfig2), customRules) } - private func getTestTextFile() -> SwiftLintFile { - SwiftLintFile(path: "\(testResourcesPath)/test.txt")! + private func violations(forExample example: Example, customRules: [String: Any]) throws -> [StyleViolation] { + let configDict: [String: Any] = [ + "only_rules": ["custom_rules", "superfluous_disable_command"], + "custom_rules": customRules, + ] + let configuration = try SwiftLintCore.Configuration(dict: configDict) + return SwiftLintTestHelpers.violations( + example.skipWrappingInCommentTest(), + config: configuration + ) + } + + private func configuration(withIdentifier identifier: String, configurationDict: [String: Any]) -> Configuration { + var regexConfig = Configuration(identifier: identifier) + do { + try regexConfig.apply(configuration: configurationDict) + } catch { + XCTFail("Failed regex config") + } + return regexConfig + } + + private func customRules(withConfigurations configurations: [Configuration]) -> CustomRules { + var customRuleConfiguration = CustomRulesConfiguration() + customRuleConfiguration.customRuleConfigurations = configurations + var customRules = CustomRules() + customRules.configuration = customRuleConfiguration + return customRules + } +} + +private extension StyleViolation { + func isSuperfluousDisableCommandViolation(for ruleIdentifier: String) -> Bool { + self.ruleIdentifier == SuperfluousDisableCommandRule.description.identifier && + reason.contains("SwiftLint rule '\(ruleIdentifier)' did not trigger a violation") } }