mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
Throw error on bad expiring todo date format (#3626)
This commit is contained in:
@@ -48,6 +48,10 @@
|
||||
[JP Simard](https://github.com/jpsim)
|
||||
[#3920](https://github.com/realm/SwiftLint/issues/3920)
|
||||
|
||||
* Error by default on bad expiring todo date formatting.
|
||||
[Christopher Hale](https://github.com/chrispomeroyhale)
|
||||
[#3636](https://github.com/realm/SwiftLint/pull/3626)
|
||||
|
||||
## 0.47.0: Smart Appliance
|
||||
|
||||
#### Breaking
|
||||
|
||||
@@ -5,6 +5,7 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
|
||||
enum ExpiryViolationLevel {
|
||||
case approachingExpiry
|
||||
case expired
|
||||
case badFormatting
|
||||
|
||||
var reason: String {
|
||||
switch self {
|
||||
@@ -12,13 +13,15 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
|
||||
return "TODO/FIXME is approaching its expiry and should be resolved soon."
|
||||
case .expired:
|
||||
return "TODO/FIXME has expired and must be resolved."
|
||||
case .badFormatting:
|
||||
return "Expiring TODO/FIXME is incorrectly formatted."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "expiring_todo",
|
||||
name: "ExpiringTodo",
|
||||
name: "Expiring Todo",
|
||||
description: "TODOs and FIXMEs should be resolved prior to their expiry date.",
|
||||
kind: .lint,
|
||||
nonTriggeringExamples: [
|
||||
@@ -36,7 +39,8 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
|
||||
Example("// TODO: [10/14/2019]\n"),
|
||||
Example("// FIXME: [10/14/2019]\n"),
|
||||
Example("// FIXME: [1/14/2019]\n"),
|
||||
Example("// FIXME: [10/4/2019]\n")
|
||||
Example("// FIXME: [10/14/2019]\n"),
|
||||
Example("// TODO: [9999/14/10]\n")
|
||||
]
|
||||
)
|
||||
|
||||
@@ -48,7 +52,7 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
|
||||
let regex = #"""
|
||||
\b(?:TODO|FIXME)(?::|\b)(?:(?!\b(?:TODO|FIXME)(?::|\b)).)*?\#
|
||||
\\#(configuration.dateDelimiters.opening)\#
|
||||
(\d{1,4}\\#(configuration.dateSeparator)\d{1,2}\\#(configuration.dateSeparator)\d{1,4})\#
|
||||
(\d{1,4}\\#(configuration.dateSeparator)\d{1,4}\\#(configuration.dateSeparator)\d{1,4})\#
|
||||
\\#(configuration.dateDelimiters.closing)
|
||||
"""#
|
||||
|
||||
@@ -57,8 +61,7 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
|
||||
syntaxKinds.allSatisfy({ $0.isCommentLike }),
|
||||
checkingResult.numberOfRanges > 1,
|
||||
case let range = checkingResult.range(at: 1),
|
||||
let date = expiryDate(file: file, range: range),
|
||||
let violationLevel = self.violationLevel(for: date),
|
||||
let violationLevel = self.violationLevel(for: expiryDate(file: file, range: range)),
|
||||
let severity = self.severity(for: violationLevel) else {
|
||||
return nil
|
||||
}
|
||||
@@ -90,10 +93,15 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
|
||||
return configuration.approachingExpirySeverity.severity
|
||||
case .expired:
|
||||
return configuration.expiredSeverity.severity
|
||||
case .badFormatting:
|
||||
return configuration.badFormattingSeverity.severity
|
||||
}
|
||||
}
|
||||
|
||||
private func violationLevel(for expiryDate: Date) -> ExpiryViolationLevel? {
|
||||
private func violationLevel(for expiryDate: Date?) -> ExpiryViolationLevel? {
|
||||
guard let expiryDate = expiryDate else {
|
||||
return .badFormatting
|
||||
}
|
||||
guard expiryDate.isAfterToday else {
|
||||
return .expired
|
||||
}
|
||||
|
||||
@@ -12,14 +12,24 @@ public struct ExpiringTodoConfiguration: RuleConfiguration, Equatable {
|
||||
}
|
||||
|
||||
public var consoleDescription: String {
|
||||
return "(approaching_expiry_severity) \(approachingExpirySeverity.consoleDescription), " +
|
||||
"(reached_or_passed_expiry_severity) \(expiredSeverity.consoleDescription)"
|
||||
let descriptions = [
|
||||
"approaching_expiry_severity: \(approachingExpirySeverity.consoleDescription)",
|
||||
"expired_severity: \(expiredSeverity.consoleDescription)",
|
||||
"bad_formatting_severity: \(badFormattingSeverity.consoleDescription)",
|
||||
"approaching_expiry_threshold: \(approachingExpiryThreshold)",
|
||||
"date_format: \(dateFormat)",
|
||||
"date_delimiters: { opening: \(dateDelimiters.opening)", "closing: \(dateDelimiters.closing) }",
|
||||
"date_separator: \(dateSeparator)"
|
||||
]
|
||||
return descriptions.joined(separator: ", ")
|
||||
}
|
||||
|
||||
private(set) var approachingExpirySeverity: SeverityConfiguration
|
||||
|
||||
private(set) var expiredSeverity: SeverityConfiguration
|
||||
|
||||
private(set) var badFormattingSeverity: SeverityConfiguration
|
||||
|
||||
// swiftlint:disable:next todo
|
||||
/// The number of days prior to expiry before the TODO emits a violation
|
||||
private(set) var approachingExpiryThreshold: Int
|
||||
@@ -33,12 +43,14 @@ public struct ExpiringTodoConfiguration: RuleConfiguration, Equatable {
|
||||
public init(
|
||||
approachingExpirySeverity: SeverityConfiguration = .init(.warning),
|
||||
expiredSeverity: SeverityConfiguration = .init(.error),
|
||||
badFormattingSeverity: SeverityConfiguration = .init(.error),
|
||||
approachingExpiryThreshold: Int = 15,
|
||||
dateFormat: String = "MM/dd/yyyy",
|
||||
dateDelimiters: DelimiterConfiguration = .default,
|
||||
dateSeparator: String = "/") {
|
||||
self.approachingExpirySeverity = approachingExpirySeverity
|
||||
self.expiredSeverity = expiredSeverity
|
||||
self.badFormattingSeverity = badFormattingSeverity
|
||||
self.approachingExpiryThreshold = approachingExpiryThreshold
|
||||
self.dateDelimiters = dateDelimiters
|
||||
self.dateFormat = dateFormat
|
||||
@@ -56,6 +68,9 @@ public struct ExpiringTodoConfiguration: RuleConfiguration, Equatable {
|
||||
if let expiredConfiguration = configurationDict["expired_severity"] {
|
||||
try expiredSeverity.apply(configuration: expiredConfiguration)
|
||||
}
|
||||
if let badFormattingConfiguration = configurationDict["bad_formatting_severity"] {
|
||||
try badFormattingSeverity.apply(configuration: badFormattingConfiguration)
|
||||
}
|
||||
if let approachingExpiryThreshold = configurationDict["approaching_expiry_threshold"] as? Int {
|
||||
self.approachingExpiryThreshold = approachingExpiryThreshold
|
||||
}
|
||||
|
||||
@@ -134,6 +134,18 @@ class ExpiringTodoRuleTests: XCTestCase {
|
||||
XCTAssertEqual(violations[0].location.line, 2)
|
||||
}
|
||||
|
||||
func testBadExpiryTodoFormat() throws {
|
||||
let ruleConfig: ExpiringTodoConfiguration = .init(
|
||||
dateFormat: "dd/yyyy/MM"
|
||||
)
|
||||
config = makeConfiguration(with: ruleConfig)
|
||||
|
||||
let example = Example("fatalError() // TODO: [31/01/2020] Implement")
|
||||
let violations = self.violations(example)
|
||||
XCTAssertEqual(violations.count, 1)
|
||||
XCTAssertEqual(violations.first?.reason, "Expiring TODO/FIXME is incorrectly formatted.")
|
||||
}
|
||||
|
||||
private func violations(_ example: Example) -> [StyleViolation] {
|
||||
return SwiftLintFrameworkTests.violations(example, config: config)
|
||||
}
|
||||
@@ -155,7 +167,7 @@ class ExpiringTodoRuleTests: XCTestCase {
|
||||
daysToAdvance = ruleConfiguration.approachingExpiryThreshold
|
||||
case .expired?:
|
||||
daysToAdvance = 0
|
||||
case nil:
|
||||
case .badFormatting?, nil:
|
||||
daysToAdvance = ruleConfiguration.approachingExpiryThreshold + 1
|
||||
}
|
||||
|
||||
@@ -174,6 +186,7 @@ class ExpiringTodoRuleTests: XCTestCase {
|
||||
serializedConfig = [
|
||||
"expired_severity": config.expiredSeverity.severity.rawValue,
|
||||
"approaching_expiry_severity": config.approachingExpirySeverity.severity.rawValue,
|
||||
"bad_formatting_severity": config.badFormattingSeverity.severity.rawValue,
|
||||
"approaching_expiry_threshold": config.approachingExpiryThreshold,
|
||||
"date_format": config.dateFormat,
|
||||
"date_delimiters": [
|
||||
|
||||
Reference in New Issue
Block a user