Skip to content

Commit bf5d0f3

Browse files
committed
basic config file support
1 parent e0b1f98 commit bf5d0f3

File tree

7 files changed

+81
-40
lines changed

7 files changed

+81
-40
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ INSTALL_PATH = $(PREFIX)/bin/$(TOOL_NAME)
1010
BUILD_PATH = $(shell swift build --show-bin-path -c $(CONFIGURATION))/$(TOOL_NAME)
1111

1212
SWIFTC_FLAGS = -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx10.13"
13-
1413
CONFIGURATION = debug
1514

1615
debug: generate_version

Sources/stringray/Commands/LintCommand.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ struct LintCommand: Command {
6060
return
6161
}
6262

63+
let config: Linter.Config
64+
if let configFile = commandArgs.configFile ?? localFileSystem.currentWorkingDirectory?.appending(component: Linter.fileName), localFileSystem.exists(configFile) {
65+
let url = URL(fileURLWithPath: configFile.asString)
66+
config = try Linter.Config(url: url)
67+
} else {
68+
config = Linter.Config()
69+
}
70+
6371
let lintInput: [LintInput]
6472
var reporter: Reporter = ConsoleReporter()
6573
if commandArgs.inputFile.isEmpty {
@@ -75,7 +83,7 @@ struct LintCommand: Command {
7583
} else {
7684
lintInput = inputs(from: commandArgs.inputFile)
7785
}
78-
try lint(inputs: lintInput, reporter: reporter)
86+
try lint(inputs: lintInput, reporter: reporter, config: config)
7987
}
8088

8189
private func inputs(from files: [AbsolutePath]) -> [LintInput] {
@@ -127,7 +135,7 @@ struct LintCommand: Command {
127135
}
128136

129137
private func listRules() {
130-
let linter = Linter(excluded: [])
138+
let linter = Linter()
131139
let rules = linter.rules
132140
let columns = [
133141
TextTableColumn(header: "id"),
@@ -147,10 +155,10 @@ struct LintCommand: Command {
147155
print(table.render())
148156
}
149157

150-
private func lint(inputs: [LintInput], reporter: Reporter) throws {
158+
private func lint(inputs: [LintInput], reporter: Reporter, config: Linter.Config) throws {
151159
var loader = StringsTableLoader()
152160
loader.options = [.lineNumbers]
153-
let linter = Linter(reporter: reporter)
161+
let linter = Linter(reporter: reporter, config: config)
154162
try inputs.forEach {
155163
print("Linting: \($0.tableName)")
156164
let table = try loader.load(url: $0.resourceURL, name: $0.tableName, base: $0.locale)

Sources/stringray/Lint Rules/LintRule.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ import Foundation
99

1010
protocol LintRule {
1111
var info: RuleInfo { get }
12-
func scan(table: StringsTable, url: URL) throws -> [LintRuleViolation]
12+
func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation]
1313
}
1414

1515
struct RuleInfo {
1616
let identifier: String
1717
let name: String
1818
let description: String
19+
let severity: Severity
1920
}
2021

21-
enum Severity: String, CustomStringConvertible {
22+
enum Severity: String, CustomStringConvertible, Decodable {
2223
case warning
2324
case error
2425

Sources/stringray/Lint Rules/Linter.swift

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,39 @@ import Foundation
99
import Yams
1010

1111
struct Linter {
12+
struct Config: Decodable {
13+
struct Rule: Decodable {
14+
let severity: Severity
15+
}
16+
let included: [String]
17+
let excluded: [String]
18+
let rules: [String: Rule]
19+
20+
private enum CodingKeys: String, CodingKey {
21+
case included
22+
case excluded
23+
case rules
24+
}
25+
26+
init() {
27+
self.included = []
28+
self.excluded = []
29+
self.rules = [:]
30+
}
31+
32+
init(url: URL) throws {
33+
let string = try String(contentsOf: url, encoding: .utf8)
34+
self = try YAMLDecoder().decode(Config.self, from: string, userInfo: [:])
35+
}
36+
37+
init(from decoder: Decoder) throws {
38+
let container = try decoder.container(keyedBy: CodingKeys.self)
39+
self.included = try container.decodeIfPresent([String].self, forKey: .included) ?? []
40+
self.excluded = try container.decodeIfPresent([String].self, forKey: .excluded) ?? []
41+
self.rules = try container.decodeIfPresent([String: Rule].self, forKey: .rules) ?? [:]
42+
}
43+
}
44+
1245
static let fileName = ".stringray.yml"
1346

1447
static let allRules: [LintRule] = [
@@ -22,32 +55,32 @@ struct Linter {
2255
}
2356

2457
let rules: [LintRule]
25-
let reporter: Reporter
58+
private let reporter: Reporter
59+
private let config: Config
2660

27-
init(rules: [LintRule] = Linter.allRules, reporter: Reporter = ConsoleReporter()) {
61+
init(rules: [LintRule] = Linter.allRules, reporter: Reporter = ConsoleReporter(), config: Config = Config()) {
2862
self.rules = rules
2963
self.reporter = reporter
30-
}
31-
32-
init(excluded: Set<String> = []) {
33-
let rules = Linter.allRules.filter {
34-
!excluded.contains($0.info.identifier)
35-
}
36-
self.init(rules: rules)
37-
}
38-
39-
init(path: String = Linter.fileName, rootPath: String? = nil) throws {
40-
let rootURL = URL(fileURLWithPath: rootPath ?? FileManager.default.currentDirectoryPath, isDirectory: true)
41-
let fullPathURL = URL(fileURLWithPath: path, relativeTo: rootURL)
42-
let yamlString = try String(contentsOf: fullPathURL, encoding: .utf8)
43-
let dict = try Yams.load(yaml: yamlString) as? [String: Any]
44-
let excluded = dict?["excluded"] as? [String] ?? []
45-
self.init(excluded: Set(excluded))
64+
self.config = config
4665
}
4766

4867
private func run(on table: StringsTable, url: URL) throws -> [LintRuleViolation] {
49-
return try rules.flatMap {
50-
try $0.scan(table: table, url: url)
68+
var runnableRules = self.rules
69+
70+
let includedRules = Set(config.included)
71+
if !includedRules.isEmpty {
72+
runnableRules.removeAll { (rule) -> Bool in
73+
!includedRules.contains(rule.info.identifier)
74+
}
75+
}
76+
77+
let excludedRules = Set(config.excluded)
78+
runnableRules.removeAll { (rule) -> Bool in
79+
excludedRules.contains(rule.info.identifier)
80+
}
81+
82+
return try runnableRules.flatMap {
83+
try $0.scan(table: table, url: url, config: config.rules[$0.info.identifier])
5184
}
5285
}
5386

Sources/stringray/Lint Rules/MissingLocalizationLintRule.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import Foundation
99

1010
struct MissingLocalizationLintRule: LintRule {
11-
let info: RuleInfo = RuleInfo(identifier: "missing_localization", name: "Missing Localization", description: "")
11+
let info: RuleInfo = RuleInfo(identifier: "missing_localization", name: "Missing Localization", description: "", severity: .warning)
1212

13-
func scan(table: StringsTable, url: URL) throws -> [LintRuleViolation] {
14-
return scanEntries(table: table, url: url) + scanDictEntries(table: table, url: url)
13+
func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] {
14+
return scanEntries(table: table, url: url, config: config) + scanDictEntries(table: table, url: url, config: config)
1515
}
1616

17-
private func scanEntries(table: StringsTable, url: URL) -> [LintRuleViolation] {
17+
private func scanEntries(table: StringsTable, url: URL, config: Linter.Config.Rule?) -> [LintRuleViolation] {
1818
var violations: [LintRuleViolation] = []
1919
var entries = table.entries
2020
entries.removeValue(forKey: table.base)
@@ -25,14 +25,14 @@ struct MissingLocalizationLintRule: LintRule {
2525
let file = URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url)
2626
let location = LintRuleViolation.Location(file: file, line: nil)
2727
let reason = "Missing \(missingEntry.key)"
28-
let violation = LintRuleViolation(locale: entry.key, location: location, severity: .warning, reason: reason)
28+
let violation = LintRuleViolation(locale: entry.key, location: location, severity: config?.severity ?? info.severity, reason: reason)
2929
violations.append(violation)
3030
}
3131
}
3232
return violations
3333
}
3434

35-
private func scanDictEntries(table: StringsTable, url: URL) -> [LintRuleViolation] {
35+
private func scanDictEntries(table: StringsTable, url: URL, config: Linter.Config.Rule?) -> [LintRuleViolation] {
3636
var violations: [LintRuleViolation] = []
3737
var dictEntries = table.dictEntries
3838
dictEntries.removeValue(forKey: table.base)
@@ -43,7 +43,7 @@ struct MissingLocalizationLintRule: LintRule {
4343
let file = URL(fileURLWithPath: "\(dictEntry.key.identifier).lproj/\(table.name).stringsdict", relativeTo: url)
4444
let location = LintRuleViolation.Location(file: file, line: nil)
4545
let reason = "Missing \(missingDictEntry.key)"
46-
let violation = LintRuleViolation(locale: dictEntry.key, location: location, severity: .warning, reason: reason)
46+
let violation = LintRuleViolation(locale: dictEntry.key, location: location, severity: config?.severity ?? info.severity, reason: reason)
4747
violations.append(violation)
4848
}
4949
}

Sources/stringray/Lint Rules/MissingPlaceholderLintRule.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import Foundation
99

1010
struct MissingPlaceholderLintRule: LintRule {
11-
let info: RuleInfo = RuleInfo(identifier: "missing_placeholder", name: "Missing Placeholder", description: "")
11+
let info: RuleInfo = RuleInfo(identifier: "missing_placeholder", name: "Missing Placeholder", description: "", severity: .error)
1212

13-
func scan(table: StringsTable, url: URL) throws -> [LintRuleViolation] {
13+
func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] {
1414
var violations: [LintRuleViolation] = []
1515
var placeholders: [String: [PlaceholderType]] = [:]
1616
try table.baseEntries.forEach {
@@ -24,7 +24,7 @@ struct MissingPlaceholderLintRule: LintRule {
2424
let line = $0.location?.line
2525
let location = LintRuleViolation.Location(file: file, line: line)
2626
let reason = "Mismatched placeholders \($0.key)"
27-
let violation = LintRuleViolation(locale: entry.key, location: location, severity: .error, reason: reason)
27+
let violation = LintRuleViolation(locale: entry.key, location: location, severity: config?.severity ?? info.severity, reason: reason)
2828
violations.append(violation)
2929
}
3030
}

Sources/stringray/Lint Rules/OrphanedLocalizationLintRule.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import Foundation
99

1010
struct OrphanedLocalizationLintRule: LintRule {
11-
let info: RuleInfo = RuleInfo(identifier: "orphaned_localization", name: "Orphaned Localization", description: "")
11+
let info: RuleInfo = RuleInfo(identifier: "orphaned_localization", name: "Orphaned Localization", description: "", severity: .warning)
1212

13-
func scan(table: StringsTable, url: URL) throws -> [LintRuleViolation] {
13+
func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] {
1414
var violations: [LintRuleViolation] = []
1515
var entries = table.entries
1616
entries.removeValue(forKey: table.base)
@@ -22,7 +22,7 @@ struct OrphanedLocalizationLintRule: LintRule {
2222
guard let line = orphanedEntry.location?.line else { continue }
2323
let location = LintRuleViolation.Location(file: file, line: line)
2424
let reason = "Orphaned \(orphanedEntry.key)"
25-
let violation = LintRuleViolation(locale: entry.key, location: location, severity: .warning, reason: reason)
25+
let violation = LintRuleViolation(locale: entry.key, location: location, severity: config?.severity ?? info.severity, reason: reason)
2626
violations.append(violation)
2727
}
2828
}

0 commit comments

Comments
 (0)