Skip to content

Commit

Permalink
Throw error on bad expiring todo date format (realm#3626)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrispomeroyhale authored and coffmark committed Apr 11, 2022
1 parent cab89d3 commit 87ee356
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 14 additions & 6 deletions Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@ public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule {
enum ExpiryViolationLevel {
case approachingExpiry
case expired
case badFormatting

var reason: String {
switch self {
case .approachingExpiry:
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: [
Expand All @@ -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")
]
)

Expand All @@ -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)
"""#

Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}
Expand Down
15 changes: 14 additions & 1 deletion Tests/SwiftLintFrameworkTests/ExpiringTodoRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -155,7 +167,7 @@ class ExpiringTodoRuleTests: XCTestCase {
daysToAdvance = ruleConfiguration.approachingExpiryThreshold
case .expired?:
daysToAdvance = 0
case nil:
case .badFormatting?, nil:
daysToAdvance = ruleConfiguration.approachingExpiryThreshold + 1
}

Expand All @@ -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": [
Expand Down

0 comments on commit 87ee356

Please sign in to comment.