Skip to content

Commit

Permalink
allow different levels of severity (error, warning)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicorichard committed Jun 7, 2024
1 parent 20af868 commit 7ab050e
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 38 deletions.
15 changes: 8 additions & 7 deletions Sources/StringCatalogValidator/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ import StringCatalogDecodable

public protocol Rule {
static var name: String { get }
var severity: Severity { get }
typealias Entry = StringCatalog.Entry
typealias Reason = Validator.Reason
func validate(key: String, value: Entry) -> [Reason]
typealias Failure = Validator.Reason
func validate(key: String, value: Entry) -> [Failure]
}

extension Rule {
func fail(message: String) -> [Reason] {
func fail(message: String) -> [Failure] {
[
Validator.Reason(rule: Self.name, message: message)
Validator.Reason(rule: self, message: message)
]
}

func fail(message: String) -> Reason {
Validator.Reason(rule: Self.name, message: message)
func fail(message: String) -> Failure {
Validator.Reason(rule: self, message: message)
}

var success: [Reason] {
var success: [Failure] {
[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import StringCatalogDecodable
extension Rules {
public struct RequireExtractionState: Rule {
let states: [String]
public var severity: Severity = .error
public static let name = "require-extraction-state"

public init(in states: [String]) {
Expand All @@ -22,7 +23,7 @@ extension Rules {
ExtractionState.automatic.rawValue
}

public func validate(key: String, value: Entry) -> [Reason] {
public func validate(key: String, value: Entry) -> [Failure] {
let actualState = value.extractionState ?? Self.defaultExtractionState

if (states.contains(actualState)) { return success }
Expand All @@ -35,6 +36,7 @@ extension Rules {

public struct RejectExtractionState: Rule {
let states: [String]
public var severity: Severity = .error
public static let name = "reject-extraction-state"

public init(state: String?) {
Expand All @@ -53,7 +55,7 @@ extension Rules {
ExtractionState.automatic.rawValue
}

public func validate(key: String, value: Entry) -> [Reason] {
public func validate(key: String, value: Entry) -> [Failure] {
let state = value.extractionState ?? Self.defaultExtractionState
if (!states.contains(state)) { return success }

Expand Down
3 changes: 2 additions & 1 deletion Sources/StringCatalogValidator/Rules/RequireLocales.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
extension Rules {
public struct RequireLocale: Rule {
let locales: [String]
public var severity: Severity = .error
public static let name = "require-locale"

public init(in locales: [String]) {
self.locales = locales
}

public func validate(key: String, value: Entry) -> [Reason] {
public func validate(key: String, value: Entry) -> [Failure] {
let missingLocales = locales.filter { language in
value.localizations?[language] == nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import StringCatalogDecodable
extension Rules {
public struct RequireLocalizationState: Rule {
let states: [String]
public var severity: Severity = .error
public static let name = "require-localization-state"

public init(in states: [String]) {
Expand All @@ -22,7 +23,7 @@ extension Rules {
"empty"
}

public func validate(key: String, value: Entry) -> [Reason] {
public func validate(key: String, value: Entry) -> [Failure] {
guard let localizations = value.localizations else {
if states.contains(Self.emptyLocalizationState) {
return success
Expand Down Expand Up @@ -52,6 +53,7 @@ extension Rules {
extension Rules {
public struct RejectLocalizationState: Rule {
let states: [String]
public var severity: Severity = .error
public static let name = "reject-localization-state"

public init(in states: [String]) {
Expand All @@ -66,7 +68,7 @@ extension Rules {
self.states = [state]
}

public func validate(key: String, value: Entry) -> [Reason] {
public func validate(key: String, value: Entry) -> [Failure] {
guard let localizations = value.localizations else { return success }

return localizations.flatMap { key, value in
Expand Down
4 changes: 4 additions & 0 deletions Sources/StringCatalogValidator/Severity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public enum Severity: Equatable, Hashable {
case error
case warning
}
4 changes: 2 additions & 2 deletions Sources/StringCatalogValidator/Validator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ extension Validator {
public let validations: [Reason]
}

public struct Reason: Equatable {
public let rule: String
public struct Reason {
public let rule: Rule
public let message: String
}
}
62 changes: 38 additions & 24 deletions Sources/XCStringsLint/XCStringsLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,57 +37,71 @@ struct XCStringsLint: ParsableCommand {
let catalog = try StringCatalog.load(from: path)

let rules = if let config {
buildRules(using: try Config.load(from: config))
try buildRules(using: try Config.load(from: config))
} else {
buildRules()
}

let results = validate(catalog: catalog, with: rules)

for result in results {
print("Validation failed for key: `\(result.key)`")
if result.validations.map(\.rule.severity).contains(.error) {
print("[Error] Validation failed for key: `\(result.key)`")
} else {
print("[Warning] Validation failed for key: `\(result.key)`")
}

for validation in result.validations {
print(" - \(validation.message)")
}
}

if !results.isEmpty {
let errorCount = results.flatMap { result in
result.validations
}
.filter { $0.rule.severity == .error }

if !errorCount.isEmpty {
print("""
[Error]: Found \(results.count) validation issues in catalog: \(path)
[Error]: Found \(results.count) validation issues, \(errorCount) serious in catalog: \(path)
""")
throw ExitCode.failure
} else {
print("""
[Warning]: Found \(results.count) validation issues in catalog: \(path)
""")
}
}

func buildRules(using config: Config) -> [Rule] {
var rules: [Rule] = []

for (ruleName, rule) in config.rules {
func buildRules(using config: Config) throws -> [Rule] {
var rules = try config.rules.compactMap { (ruleName, rule) -> Rule? in
switch ruleName {
case Rules.RequireExtractionState.name:
rules.append(
Rules.RequireExtractionState(in: rule.values)
)
var domainRule = Rules.RequireExtractionState(in: rule.values)
domainRule.severity = rule.severity.toDomain()
return domainRule
case Rules.RejectExtractionState.name:
rules.append(
Rules.RejectExtractionState(in: rule.values)
)
var domainRule = Rules.RejectExtractionState(in: rule.values)
domainRule.severity = rule.severity.toDomain()
return domainRule
case Rules.RequireLocale.name:
rules.append(
Rules.RequireLocale(in: rule.values)
)
var domainRule = Rules.RequireLocale(in: rule.values)
domainRule.severity = rule.severity.toDomain()
return domainRule
case Rules.RequireLocalizationState.name:
rules.append(
Rules.RequireLocalizationState(in: rule.values)
)
var domainRule = Rules.RequireLocalizationState(in: rule.values)
domainRule.severity = rule.severity.toDomain()
return domainRule
case Rules.RejectLocalizationState.name:
rules.append(
Rules.RejectLocalizationState(in: rule.values)
)
var domainRule = Rules.RejectLocalizationState(in: rule.values)
domainRule.severity = rule.severity.toDomain()
return domainRule
default:
break
throw ValidationError("Unknown rule: \(ruleName)")
}
}

Expand Down
10 changes: 10 additions & 0 deletions Sources/XCStringsLint/helpers/Config+toDomain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import StringCatalogValidator

extension Config.Rule.Severity {
func toDomain() -> Severity {
return switch self {
case .error: .error
case .warning: .warning
}
}
}
19 changes: 19 additions & 0 deletions Sources/XCStringsLint/models/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ struct Config: Decodable {

struct Rule: Decodable {
let values: [String]
let severity: Severity

enum Severity: String, Decodable {
case warning
case error
}

enum CodingKeys: String, CodingKey {
case value
case values
case severity
}

init(from decoder: Decoder) throws {
Expand All @@ -16,6 +23,18 @@ struct Config: Decodable {
} else {
values = try container.decode([String].self, forKey: .values)
}

if let severity = try? container.decodeIfPresent(String.self, forKey: .severity) {
guard let severity = Severity(rawValue: severity) else {
throw DecodingError.typeMismatch(
Severity.self,
.init(codingPath: container.codingPath, debugDescription: "Invalid severity value")
)
}
self.severity = severity
} else {
severity = .error
}
}
}
}

0 comments on commit 7ab050e

Please sign in to comment.