diff --git a/CHANGELOG.md b/CHANGELOG.md index ce73ab064..3cfc53580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ `class` for class-only protocols. [Ornithologist Coder](https://github.com/ornithocoder) [#2283](https://github.com/realm/SwiftLint/issues/2283) + +* Add options `prefix_pattern` and `suffix_pattern` to rule `file_name`. + [Cihat Gündüz](https://github.com/Dschee) + [#2309](https://github.com/realm/SwiftLint/issues/2309) * Add new bool config option `if_only` to rule `conditional_returns_on_newline` to specify that the rule should only be applied to `if` statements. diff --git a/Source/SwiftLintFramework/Extensions/String+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/String+SwiftLint.swift index 18681e69c..f34a5012d 100644 --- a/Source/SwiftLintFramework/Extensions/String+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/String+SwiftLint.swift @@ -71,6 +71,10 @@ extension String { return fromIndex.. String { return bridge().absolutePathRepresentation().bridge().standardizingPath } diff --git a/Source/SwiftLintFramework/Rules/FileNameRule.swift b/Source/SwiftLintFramework/Rules/FileNameRule.swift index 6b6180442..650286195 100644 --- a/Source/SwiftLintFramework/Rules/FileNameRule.swift +++ b/Source/SwiftLintFramework/Rules/FileNameRule.swift @@ -15,7 +15,12 @@ private extension Dictionary where Key: ExpressibleByStringLiteral { } public struct FileNameRule: ConfigurationProviderRule, OptInRule { - public var configuration = FileNameConfiguration(severity: .warning, excluded: ["main.swift", "LinuxMain.swift"]) + public var configuration = FileNameConfiguration( + severity: .warning, + excluded: ["main.swift", "LinuxMain.swift"], + prefixPattern: "", + suffixPattern: "\\+.*" + ) public init() {} @@ -33,7 +38,20 @@ public struct FileNameRule: ConfigurationProviderRule, OptInRule { return [] } - let typeInFileName = fileName.components(separatedBy: CharacterSet(charactersIn: "+.")).first ?? fileName + let prefixRegex = regex("\\A\(configuration.prefixPattern)") + let suffixRegex = regex("\(configuration.suffixPattern)\\z") + + var typeInFileName = fileName.components(separatedBy: ".").first ?? fileName + + if let match = prefixRegex.firstMatch(in: typeInFileName, options: [], range: typeInFileName.fullNSRange), + let range = typeInFileName.nsrangeToIndexRange(match.range) { + typeInFileName.removeSubrange(range) + } + + if let match = suffixRegex.firstMatch(in: typeInFileName, options: [], range: typeInFileName.fullNSRange), + let range = typeInFileName.nsrangeToIndexRange(match.range) { + typeInFileName.removeSubrange(range) + } let allDeclaredTypeNames = file.structure.dictionary.recursiveDeclaredTypeNames() guard !allDeclaredTypeNames.isEmpty, !allDeclaredTypeNames.contains(typeInFileName) else { diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileNameConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileNameConfiguration.swift index f8dc51b72..e299bced7 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileNameConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileNameConfiguration.swift @@ -6,10 +6,15 @@ public struct FileNameConfiguration: RuleConfiguration, Equatable { private(set) public var severity: SeverityConfiguration private(set) public var excluded: Set + private(set) public var prefixPattern: String + private(set) public var suffixPattern: String - public init(severity: ViolationSeverity, excluded: [String] = []) { + public init(severity: ViolationSeverity, excluded: [String] = [], + prefixPattern: String = "", suffixPattern: String = "\\+.*") { self.severity = SeverityConfiguration(severity) self.excluded = Set(excluded) + self.prefixPattern = prefixPattern + self.suffixPattern = suffixPattern } public mutating func apply(configuration: Any) throws { @@ -23,10 +28,18 @@ public struct FileNameConfiguration: RuleConfiguration, Equatable { if let excluded = [String].array(of: configurationDict["excluded"]) { self.excluded = Set(excluded) } + if let prefixPattern = configurationDict["prefix_pattern"] as? String { + self.prefixPattern = prefixPattern + } + if let suffixPattern = configurationDict["suffix_pattern"] as? String { + self.suffixPattern = suffixPattern + } } } public func == (lhs: FileNameConfiguration, rhs: FileNameConfiguration) -> Bool { return lhs.severity == rhs.severity && - lhs.excluded == rhs.excluded + lhs.excluded == rhs.excluded && + lhs.prefixPattern == rhs.prefixPattern && + lhs.suffixPattern == rhs.suffixPattern } diff --git a/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift b/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift index 840f6fab4..c7a916cc3 100644 --- a/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift @@ -7,11 +7,18 @@ private let fixturesDirectory = #file.bridge() .appendingPathComponent("Resources/FileNameRuleFixtures") class FileNameRuleTests: XCTestCase { - private func validate(fileName: String, excludedOverride: [String]? = nil) throws -> [StyleViolation] { + private func validate(fileName: String, excludedOverride: [String]? = nil, + prefixPattern: String? = nil, suffixPattern: String? = nil) throws -> [StyleViolation] { let file = File(path: fixturesDirectory.stringByAppendingPathComponent(fileName))! let rule: FileNameRule if let excluded = excludedOverride { rule = try FileNameRule(configuration: ["excluded": excluded]) + } else if let prefixPattern = prefixPattern, let suffixPattern = suffixPattern { + rule = try FileNameRule(configuration: ["prefix_pattern": prefixPattern, "suffix_pattern": suffixPattern]) + } else if let prefixPattern = prefixPattern { + rule = try FileNameRule(configuration: ["prefix_pattern": prefixPattern]) + } else if let suffixPattern = suffixPattern { + rule = try FileNameRule(configuration: ["suffix_pattern": suffixPattern]) } else { rule = FileNameRule() } @@ -49,4 +56,32 @@ class FileNameRuleTests: XCTestCase { func testMainDoesTriggerWithoutOverride() { XCTAssertEqual(try validate(fileName: "main.swift", excludedOverride: []).count, 1) } + + func testCustomSuffixPattern() { + XCTAssert(try validate(fileName: "BoolExtension.swift", suffixPattern: "Extensions?").isEmpty) + XCTAssert(try validate(fileName: "BoolExtensions.swift", suffixPattern: "Extensions?").isEmpty) + } + + func testCustomPrefixPattern() { + XCTAssert(try validate(fileName: "ExtensionBool.swift", prefixPattern: "Extensions?").isEmpty) + XCTAssert(try validate(fileName: "ExtensionsBool.swift", prefixPattern: "Extensions?").isEmpty) + } + + func testCustomPrefixAndSuffixPatterns() { + XCTAssert( + try validate( + fileName: "SLBoolExtension.swift", + prefixPattern: "SL", + suffixPattern: "Extensions?|\\+.*" + ).isEmpty + ) + + XCTAssert( + try validate( + fileName: "ExtensionBool+SwiftLint.swift", + prefixPattern: "Extensions?", + suffixPattern: "Extensions?|\\+.*" + ).isEmpty + ) + } } diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtension.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtension.swift new file mode 100644 index 000000000..498b59d8d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtension.swift @@ -0,0 +1,6 @@ +struct MyStruct {} +class MyClass {} + +extension Bool { + func toggle() {} +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtensions.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtensions.swift new file mode 100644 index 000000000..498b59d8d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtensions.swift @@ -0,0 +1,6 @@ +struct MyStruct {} +class MyClass {} + +extension Bool { + func toggle() {} +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool+SwiftLint.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool+SwiftLint.swift new file mode 100644 index 000000000..498b59d8d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool+SwiftLint.swift @@ -0,0 +1,6 @@ +struct MyStruct {} +class MyClass {} + +extension Bool { + func toggle() {} +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool.swift new file mode 100644 index 000000000..498b59d8d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool.swift @@ -0,0 +1,6 @@ +struct MyStruct {} +class MyClass {} + +extension Bool { + func toggle() {} +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionsBool.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionsBool.swift new file mode 100644 index 000000000..498b59d8d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionsBool.swift @@ -0,0 +1,6 @@ +struct MyStruct {} +class MyClass {} + +extension Bool { + func toggle() {} +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/SLBoolExtension.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/SLBoolExtension.swift new file mode 100644 index 000000000..498b59d8d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/SLBoolExtension.swift @@ -0,0 +1,6 @@ +struct MyStruct {} +class MyClass {} + +extension Bool { + func toggle() {} +}