From 9beef231754b23a610ea25e42eef54b4549300cb Mon Sep 17 00:00:00 2001 From: Chris Hale <55423+chrispomeroyhale@users.noreply.github.com> Date: Mon, 11 Apr 2022 12:20:05 -0700 Subject: [PATCH] Throw error on bad expiring todo date format (#3626) --- CHANGELOG.md | 4 ++++ .../Rules/Lint/ExpiringTodoRule.swift | 20 +++++++++++++------ .../ExpiringTodoConfiguration.swift | 19 ++++++++++++++++-- .../ExpiringTodoRuleTests.swift | 15 +++++++++++++- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3497a35..a96268292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift b/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift index 21ce7e20d..8e66e20bc 100644 --- a/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift +++ b/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift @@ -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 } diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/ExpiringTodoConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/ExpiringTodoConfiguration.swift index ce2a6033a..ed2cc9b2a 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/ExpiringTodoConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/ExpiringTodoConfiguration.swift @@ -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 } diff --git a/Tests/SwiftLintFrameworkTests/ExpiringTodoRuleTests.swift b/Tests/SwiftLintFrameworkTests/ExpiringTodoRuleTests.swift index 20b564f25..324ea660a 100644 --- a/Tests/SwiftLintFrameworkTests/ExpiringTodoRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/ExpiringTodoRuleTests.swift @@ -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": [