Skip to content

Commit

Permalink
Validate string-arrays in Android XML (fix #35) (#36)
Browse files Browse the repository at this point in the history
* Validate string-arrays in Android XML

* Print swift version in CI

* Nah

* Fix build error

* Fix tests
  • Loading branch information
stevelandeyasana authored Apr 14, 2022
1 parent 7f0f913 commit 626f0d1
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 4 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
steps:
- uses: actions/checkout@v2
# uncomment to pin swift version, but it takes several minutes.
# - uses: fwal/setup-swift@v1
# with:
# swift-version: "5.4"
#- uses: fwal/setup-swift@v1
# with:
# swift-version: "5.6"

- name: Cache SwiftPM
uses: actions/cache@v1
Expand All @@ -24,6 +24,8 @@ jobs:
restore-keys: |
${{ runner.os }}-swiftpm-deps-${{ github.workspace }}
- name: Swift Version
run: swift --version
- name: Build
run: swift build
- name: Run tests
Expand Down
15 changes: 15 additions & 0 deletions Examples/strings-base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,19 @@

<!-- Missing from translation -->
<string name="missing_from_translation">Missing from translation</string>

<!-- Good string array -->
<string-array name="good_string_array">
<item>Item 1</item>
</string-array>

<!-- String array missing from translation -->
<string-array name="translation_missing_string_array">
<item>Item 1</item>
</string-array>

<!-- String array with wrong item count -->
<string-array name="string_array_wrong_item_count">
<item>Item 1</item>
</string-array>
</resources>
16 changes: 16 additions & 0 deletions Examples/strings-translation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,20 @@

<!-- Missing from base -->
<string name="missing_from_base">Missing from base</string>

<!-- Good string array -->
<string-array name="good_string_array">
<item>Item 1</item>
</string-array>

<!-- String array missing from base -->
<string-array name="base_missing_string_array">
<item>Item 1</item>
</string-array>

<!-- String array with wrong item count -->
<string-array name="string_array_wrong_item_count">
<item>Item 1</item>
<item>Item 2</item>
</string-array>
</resources>
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ let package = Package(
.testTarget(
name: "LocheckCommandTests",
dependencies: ["LocheckCommand"]),
.testTarget(
name: "LocheckLogicTests",
dependencies: ["LocheckLogic"]),
])
15 changes: 15 additions & 0 deletions Sources/LocheckLogic/Problems.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,21 @@ struct StringHasMissingArguments: Problem, StringsProblem, Equatable {
var message: String { "'\(key)' does not include argument(s) at \(args.joined(separator: ", "))" }
}

struct StringArrayItemCountMismatch: Problem, StringsProblem, Equatable {
var base: String?
var translation: String?

var kindIdentifier: String { "string_array_item_count_mismatch" }
var uniquifyingInformation: String { "\(language)-\(key)" }
var severity: Severity { .warning }
let key: String
let language: String
let countBase: Int
let countTranslation: Int

var message: String { "'\(key)' item count mismatch in \(language): \(countTranslation) (should be \(countBase))" }
}

struct StringsdictEntryContainsNoVariablesProblem: Problem, StringsdictProblem, Equatable {
var kindIdentifier: String { "stringsdict_entry_contains_no_variables" }
var uniquifyingInformation: String { "\(key)" }
Expand Down
33 changes: 33 additions & 0 deletions Sources/LocheckLogic/Types/AndroidStringsFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ struct AndroidString: Equatable {
var line: Int { value.line }
}

struct AndroidStringArray: Equatable {
let key: String
let values: [String]
let line: Int
}

public struct AndroidStringsFile: Equatable {
let path: String
let strings: [AndroidString]
let plurals: [AndroidPlural]
let stringArrays: [AndroidStringArray]
}

public extension AndroidStringsFile {
Expand All @@ -42,6 +49,7 @@ public extension AndroidStringsFile {
}

var strings = [AndroidString]()
var stringArrays = [AndroidStringArray]()
var plurals = [AndroidPlural]()

var seenKeys = Set<String>()
Expand Down Expand Up @@ -87,6 +95,30 @@ public extension AndroidStringsFile {
key: key,
value: FormatString(string: element.text ?? "", path: path, line: element.lineNumberStart)))
}
case "string-array":
var values = [String]()
for child in element.childElements {
guard child.name == "item" else {
problemReporter.report(
XMLSchemaProblem(message: "Item \(i + 1) has a malformed child (not an 'item')"),
path: path,
lineNumber: child.lineNumberStart)
continue
}
if let cdata = child.CDATA {
guard let string = String(data: cdata, encoding: .utf8) else {
problemReporter.report(
CDATACannotBeDecoded(key: key),
path: path,
lineNumber: child.lineNumberStart)
continue
}
values.append(string)
} else {
values.append(child.text ?? "")
}
}
stringArrays.append(AndroidStringArray(key: key, values: values, line: element.lineNumberStart))
case "plurals":
var values = [String: FormatString]()
for child in element.childElements {
Expand Down Expand Up @@ -124,5 +156,6 @@ public extension AndroidStringsFile {

self.strings = strings
self.plurals = plurals
self.stringArrays = stringArrays
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,18 @@ func validateAndroidStrings(
translationLanguageName: translationLanguageName,
problemReporter: problemReporter)

validateKeyPresence(
basePath: base.path,
baseKeys: Set(base.stringArrays.map(\.key)),
baseLineNumberMap: base.stringArrays.lo_makeDictionary(makeKey: \.key, makeValue: \.line),
translationPath: translation.path,
translationKeys: Set(translation.stringArrays.map(\.key)),
translationLineNumberMap: translation.stringArrays.lo_makeDictionary(makeKey: \.key, makeValue: \.line),
translationLanguageName: translationLanguageName,
problemReporter: problemReporter)

let baseStringMap = base.strings.lo_makeDictionary(makeKey: \.key)
let baseStringArrayMap = base.stringArrays.lo_makeDictionary(makeKey: \.key)

for translationString in translation.strings {
guard let baseString = baseStringMap[translationString.key] else {
Expand Down Expand Up @@ -146,4 +157,21 @@ func validateAndroidStrings(
}
}
}

for translationStringArray in translation.stringArrays {
guard let baseStringArray = baseStringArrayMap[translationStringArray.key] else {
continue // We already threw an error for this in validateKeyPresence()
}

if translationStringArray.values.count != baseStringArray.values.count {
problemReporter.report(
StringArrayItemCountMismatch(
key: translationStringArray.key,
language: translationLanguageName,
countBase: baseStringArray.values.count,
countTranslation: translationStringArray.values.count),
path: translation.path,
lineNumber: translationStringArray.line)
}
}
}
63 changes: 63 additions & 0 deletions Tests/LocheckCommandTests/ExecutableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,67 @@ class ExecutableTests: XCTestCase {
""")
}

func testExampleOutput_android() throws {
let binary = productsDirectory.appendingPathComponent("locheck")

let process = Process()
process.executableURL = binary
process.arguments = ["androidstrings", "Examples/strings-base.xml", "Examples/strings-translation.xml"]

let stdoutPipe = Pipe()
let stderrPipe = Pipe()
process.standardOutput = stdoutPipe
process.standardError = stderrPipe

try process.run()
process.waitUntilExit()

let stdout = String(data: stdoutPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)
let stderr = String(data: stderrPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)

XCTAssertEqual(stdout!, """
Summary:
Examples/strings-base.xml
missing_from_translation:
WARNING: 'missing_from_translation' is missing from Examples (key_missing_from_translation)
translation_missing_string_array:
WARNING: 'translation_missing_string_array' is missing from Examples (key_missing_from_translation)
Examples/strings-translation.xml
base_missing_string_array:
WARNING: 'base_missing_string_array' is missing from the base translation (key_missing_from_base)
missing_from_base:
WARNING: 'missing_from_base' is missing from the base translation (key_missing_from_base)
string_array_wrong_item_count:
WARNING: 'string_array_wrong_item_count' item count mismatch in Examples: 2 (should be 1) (string_array_item_count_mismatch)
translation_has_invalid_specifier:
ERROR: Specifier for argument 2 does not match (should be d, is lu) (string_has_invalid_argument)
Base: %s %d
Translation: %s %lu
translation_has_missing_arg:
WARNING: 'translation_has_missing_arg' does not include argument(s) at 2 (string_has_missing_arguments)
Base: %s %d
Translation: %s
translation_has_missing_phrase:
ERROR: 'translation_has_missing_phrase' does not include argument(s): object_name (phrase_has_missing_arguments)
Base: Could not add {user_name} to \\"{object_name}\\"
Translation: Could not add {user_name}
6 warnings, 2 errors
Errors found
""")

XCTAssertEqual(stderr!, """
Examples/strings-base.xml:28: warning: 'missing_from_translation' is missing from Examples (key_missing_from_translation)
Examples/strings-translation.xml:25: warning: 'missing_from_base' is missing from the base translation (key_missing_from_base)
Examples/strings-base.xml:36: warning: 'translation_missing_string_array' is missing from Examples (key_missing_from_translation)
Examples/strings-translation.xml:33: warning: 'base_missing_string_array' is missing from the base translation (key_missing_from_base)
Examples/strings-translation.xml:17: error: 'translation_has_missing_phrase' does not include argument(s): object_name (phrase_has_missing_arguments)
Examples/strings-translation.xml:21: error: Specifier for argument 2 does not match (should be d, is lu) (string_has_invalid_argument)
Examples/strings-translation.xml:22: warning: 'translation_has_missing_arg' does not include argument(s) at 2 (string_has_missing_arguments)
Examples/strings-translation.xml:38: warning: 'string_array_wrong_item_count' item count mismatch in Examples: 2 (should be 1) (string_array_item_count_mismatch)
""")
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,20 @@ class ParseAndValidateAndroidStringsTests: XCTestCase {
translationLanguageName: "demo",
problemReporter: problemReporter)

XCTAssertEqual(problemReporter.problems.count, 5)
XCTAssertEqual(problemReporter.problems.count, 8)
let problems = problemReporter.problems.map(\.problem)

XCTAssertEqual(problems.map(\.kindIdentifier), [
"key_missing_from_translation",
"key_missing_from_base",
"key_missing_from_translation",
"key_missing_from_base",
"phrase_has_missing_arguments",
"string_has_invalid_argument",
"string_has_missing_arguments",
"string_array_item_count_mismatch"
])

CastAndAssertEqual(problems[0], KeyMissingFromTranslation(key: "missing_from_translation", language: "demo"))
CastAndAssertEqual(problems[1], KeyMissingFromBase(key: "missing_from_base"))
}
Expand Down

0 comments on commit 626f0d1

Please sign in to comment.