Skip to content

Commit

Permalink
update tests to match earlier static changes, update readme to includ…
Browse files Browse the repository at this point in the history
…e rules, add new xcode 16 ignore
  • Loading branch information
nicorichard committed Jul 9, 2024
1 parent 2a99281 commit 19a4798
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 27 deletions.
56 changes: 53 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Ensure your string catalog changes are always up-to-snuff with your team's local

## Usage

### CLI
### Running
#### CLI

With a [config file](.xcstringslint.yaml) located in the same directory as the executable

Expand All @@ -16,11 +17,60 @@ swift run xcstringslint Sources/StringCatalogValidator/Resources/Localizable.xcs

To specify a config file append `--config [config file path]`

### GitHub Actions
#### GitHub Actions

See [this repository's actions for an example](.github/workflows/lint.yaml)

## Example
### Configuration

#### Rules

##### `require-locale`

Ensures that for each locale has a translation present in the `.xcstrings` file.

e.g. ensure that each key has a translation for `en` and `fr`
```yaml
rules:
require-locale:
values:
- en
- fr
```
##### `require-localization-state`

Ensures that each key has a localization state matching the provided values.

e.g. ensure that each key has a localization state of `translated`
```yaml
rules:
require-localization-state:
value: translated
```

To negate this rule use `reject-localization-state` instead.

##### `require-extraction-state`

Ensures that each key has a translation for each extraction state.

e.g. ensure that each key was automatically extracted
```yaml
rules:
require-extraction-state:
value: "automatic"
```

To negate this rule use `reject-extraction-state` instead.

#### Ignoring Keys

To ignore all validation for a particular key, either mark them as "Don't Translate" or include `[no-lint]` in the key's comment.

To ignore a specific rule, include `[no-lint:rule-name]` in the key's comment, e.g. `[no-lint:require-locale]`

## Example Output

```
$ swift run xcstringslint Sources/StringCatalogValidator/Resources/Localizable.xcstrings --require-locale en fr
Expand Down
13 changes: 13 additions & 0 deletions Sources/StringCatalogDecodable/StringCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ public struct StringCatalog: Decodable {
public var localizations: [String: Localization]?
public var comment: String?
public var extractionState: String?
public var shouldTranslate: Bool?

public init(localizations: [String : Localization]? = nil, comment: String? = nil, extractionState: String? = nil, shouldTranslate: Bool? = nil) {
self.localizations = localizations
self.comment = comment
self.extractionState = extractionState
self.shouldTranslate = shouldTranslate
}
}
}

Expand Down Expand Up @@ -40,6 +48,11 @@ public enum Localization: Decodable {
public struct StringUnit: Decodable {
public var state: String
public var value: String

public init(state: String, value: String) {
self.state = state
self.value = value
}
}
}

Expand Down
19 changes: 16 additions & 3 deletions Sources/StringCatalogValidator/Ignore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,21 @@ public struct Ignore: IgnoreProtocol {

extension Ignore {
public static let `default` = Ignore { _, rule, value in
value.comment?.contains("[no-lint]")
?? value.comment?.contains("[no-lint:\(rule)]")
?? false
if let comment = value.comment {
if comment.contains("[no-lint]") {
return true
}
if comment.contains("[no-lint:\(rule)]") {
return true
}
}

if let shouldTranslate = value.shouldTranslate {
if shouldTranslate == false {
return true
}
}

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"value" : "no translation state found"
}
}
}
},
"shouldTranslate" : false
},
"Rejects a localization if its state matches one of the provided values. Know localization states: %@" : {
"localizations" : {
Expand Down
11 changes: 7 additions & 4 deletions Sources/StringCatalogValidator/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ public protocol Rule {

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

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

var success: [Failure] {
Expand Down
6 changes: 4 additions & 2 deletions Sources/StringCatalogValidator/Validator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ extension Validator {
public let validations: [Reason]
}

public struct Reason {
public let rule: Rule
public struct Reason: Equatable {
public let name: String
public let description: String
public let severity: Severity
public let message: String
}
}
4 changes: 2 additions & 2 deletions Sources/XCStringsLint/XCStringsLint+Lint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import StringCatalogValidator
import StringCatalogDecodable
import ArgumentParser

extension XCStringsLint {
extension xcstringslint {
struct Lint: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "lint"
Expand Down Expand Up @@ -44,7 +44,7 @@ extension XCStringsLint {

// MARK: - Config Resolving

extension XCStringsLint.Lint {
extension xcstringslint.Lint {

private func findXCStringsFiles(atPath path: String) throws -> [String] {
let enumerator = FileManager.default
Expand Down
2 changes: 1 addition & 1 deletion Sources/XCStringsLint/XCStringsLint+Rules.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ArgumentParser

extension XCStringsLint {
extension xcstringslint {
struct Rules: ParsableCommand {
static var configuration = CommandConfiguration(commandName: "rules")

Expand Down
4 changes: 3 additions & 1 deletion Sources/XCStringsLint/XCStringsLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import StringCatalogDecodable
import ArgumentParser

@main
struct XCStringsLint: ParsableCommand {
struct xcstringslint: ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "Validate xcstrings",
discussion: "Ensure string catalog changes always meet your team's localization requirements.",
subcommands: [Lint.self, Rules.self],
defaultSubcommand: Lint.self
)
Expand Down
64 changes: 64 additions & 0 deletions Tests/StringCatalogValidatorTests/IgnoreTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import XCTest
import StringCatalogValidator

class IgnoreTests: XCTestCase {
func test_givenShouldTranslateIsFalse_whenIgnoreIsCalled_falseIsReturned() {
let sut = Ignore.default

let result = sut.ignore(
key: "",
rule: "",
value: .init(shouldTranslate: false)
)

XCTAssertTrue(result)
}

func test_givenShouldTranslateIsTrue_whenIgnoreIsCalled_falseIsReturned() {
let sut = Ignore.default

let result = sut.ignore(
key: "",
rule: "",
value: .init(shouldTranslate: true)
)

XCTAssertFalse(result)
}

func test_givenCommentContainsNoLint_whenIgnoreIsCalled_trueIsReturned() {
let sut = Ignore.default

let result = sut.ignore(
key: "",
rule: "",
value: .init(comment: "[no-lint]")
)

XCTAssertTrue(result)
}

func test_givenCommentContainsNoLint_whenTheSpecializedRuleMatches_falseIsReturned() {
let sut = Ignore.default

let result = sut.ignore(
key: "",
rule: "rule",
value: .init(comment: "[no-lint:rule]")
)

XCTAssertTrue(result)
}

func test_givenCommentContainsNoLint_whenTheSpecializedRuleDoesNotMatch_trueIsReturned() {
let sut = Ignore.default

let result = sut.ignore(
key: "",
rule: "rule",
value: .init(comment: "[no-lint:another-rule]")
)

XCTAssertFalse(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class RejectExtractionStateTests: XCTestCase {
let json = "{}"

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["reject-extraction-state"])
XCTAssertEqual(result.map(\.name), ["reject-extraction-state"])
}


Expand All @@ -57,6 +57,6 @@ class RejectExtractionStateTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["reject-extraction-state"])
XCTAssertEqual(result.map(\.name), ["reject-extraction-state"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RequireExtractionStateTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-extraction-state"])
XCTAssertEqual(result.map(\.name), ["require-extraction-state"])
}

func testRequireExtractionStateMatchesValue_failsForOther() throws {
Expand All @@ -49,7 +49,7 @@ class RequireExtractionStateTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-extraction-state"])
XCTAssertEqual(result.map(\.name), ["require-extraction-state"])
}

func testRequireExtractionStateMatchesAutomatic_fail() throws {
Expand All @@ -62,6 +62,6 @@ class RequireExtractionStateTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-extraction-state"])
XCTAssertEqual(result.map(\.name), ["require-extraction-state"])
}
}
6 changes: 3 additions & 3 deletions Tests/StringCatalogValidatorTests/RequireLocaleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class RequireLocaleTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-locale"])
XCTAssertEqual(result.map(\.name), ["require-locale"])
}

func testRequireLocale_givenNoVariations_multipleLocales_andFullMatch_succeeds() throws {
Expand Down Expand Up @@ -91,7 +91,7 @@ class RequireLocaleTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-locale"])
XCTAssertEqual(result.map(\.name), ["require-locale"])
}

func testRequireLocale_givenVariations_andMatchingLocale_succeeds() throws {
Expand Down Expand Up @@ -155,6 +155,6 @@ class RequireLocaleTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-locale"])
XCTAssertEqual(result.map(\.name), ["require-locale"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class RequireLocalizationStateTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-localization-state"])
XCTAssertEqual(result.map(\.name), ["require-localization-state"])
}

func test_RequireLocalizationState_withVariations_andMatchingState_succeeds() throws {
Expand Down Expand Up @@ -112,6 +112,6 @@ class RequireLocalizationStateTests: XCTestCase {
"""

let result = sut.validate(key: "key", value: try EntryDecoder.entry(from: json))
XCTAssertEqual(result.map(\.rule), ["require-localization-state"])
XCTAssertEqual(result.map(\.name), ["require-localization-state"])
}
}

0 comments on commit 19a4798

Please sign in to comment.