Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileHeaderConfiguration.swift
T
2025-09-27 19:11:46 +02:00

134 lines
5.4 KiB
Swift

import Foundation
import SwiftLintCore
struct FileHeaderConfiguration: SeverityBasedRuleConfiguration {
typealias Parent = FileHeaderRule
private static let fileNamePlaceholder = "SWIFTLINT_CURRENT_FILENAME"
private static let stringRegexOptions: NSRegularExpression.Options = [.ignoreMetacharacters]
private static let patternRegexOptions: NSRegularExpression.Options =
[.anchorsMatchLines, .dotMatchesLineSeparators]
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "required_string")
private var requiredString: String?
@ConfigurationElement(key: "required_pattern")
private var requiredPattern: String?
@ConfigurationElement(key: "forbidden_string")
private var forbiddenString: String?
@ConfigurationElement(key: "forbidden_pattern")
private var forbiddenPattern: String?
private var _forbiddenRegex: RegularExpression?
private var _requiredRegex: RegularExpression?
private static let defaultRegex = regex("\\bCopyright\\b", options: [.caseInsensitive])
mutating func apply(configuration: Any) throws(Issue) {
guard let configuration = configuration as? [String: String] else {
throw .invalidConfiguration(ruleID: Parent.identifier)
}
// Cache the created regexes if possible.
// If the pattern contains the SWIFTLINT_CURRENT_FILENAME placeholder,
// the regex will be recompiled for each validated file.
if let requiredString = configuration[$requiredString.key] {
self.requiredString = requiredString
if !requiredString.contains(Self.fileNamePlaceholder) {
_requiredRegex = try .from(
pattern: requiredString,
options: Self.stringRegexOptions,
for: Parent.identifier
)
}
} else if let requiredPattern = configuration[$requiredPattern.key] {
self.requiredPattern = requiredPattern
if !requiredPattern.contains(Self.fileNamePlaceholder) {
_requiredRegex = try .from(pattern: requiredPattern, for: Parent.identifier)
}
}
if let forbiddenString = configuration[$forbiddenString.key] {
self.forbiddenString = forbiddenString
if !forbiddenString.contains(Self.fileNamePlaceholder) {
_forbiddenRegex = try .from(
pattern: forbiddenString,
options: Self.stringRegexOptions,
for: Parent.identifier
)
}
} else if let forbiddenPattern = configuration[$forbiddenPattern.key] {
self.forbiddenPattern = forbiddenPattern
if !forbiddenPattern.contains(Self.fileNamePlaceholder) {
_forbiddenRegex = try .from(pattern: forbiddenPattern, for: Parent.identifier)
}
}
if let severityString = configuration[$severityConfiguration.key] {
try severityConfiguration.apply(configuration: severityString)
}
}
private func makeRegex(for file: SwiftLintFile,
using pattern: String,
options: NSRegularExpression.Options,
escapeFileName: Bool) -> NSRegularExpression? {
// Recompile the regex for this file...
let replacedPattern = file.path.map { path in
let fileName = path.bridge().lastPathComponent
// Replace SWIFTLINT_CURRENT_FILENAME with the filename.
let escapedName = escapeFileName ? NSRegularExpression.escapedPattern(for: fileName) : fileName
return pattern.replacingOccurrences(of: Self.fileNamePlaceholder,
with: escapedName)
} ?? pattern
do {
return try NSRegularExpression(pattern: replacedPattern, options: options)
} catch {
queuedFatalError("Failed to compile pattern '\(replacedPattern)'")
}
}
private func regexFromString(for file: SwiftLintFile, using pattern: String) -> NSRegularExpression? {
makeRegex(for: file, using: pattern, options: Self.stringRegexOptions, escapeFileName: false)
}
private func regexFromPattern(for file: SwiftLintFile, using pattern: String) -> NSRegularExpression? {
makeRegex(for: file, using: pattern, options: Self.patternRegexOptions, escapeFileName: true)
}
func forbiddenRegex(for file: SwiftLintFile) -> NSRegularExpression? {
if _forbiddenRegex != nil {
return _forbiddenRegex?.regex
}
if let regex = forbiddenString.flatMap({ regexFromString(for: file, using: $0) }) {
return regex
}
if let regex = forbiddenPattern.flatMap({ regexFromPattern(for: file, using: $0) }) {
return regex
}
if requiredPattern == nil, requiredString == nil {
return Self.defaultRegex
}
return nil
}
func requiredRegex(for file: SwiftLintFile) -> NSRegularExpression? {
if _requiredRegex != nil {
return _requiredRegex?.regex
}
if let regex = requiredString.flatMap({ regexFromString(for: file, using: $0) }) {
return regex
}
return requiredPattern.flatMap { regexFromPattern(for: file, using: $0) }
}
}