From f1a5dbfe5f5710dd91a8bf9903b59c2db015c675 Mon Sep 17 00:00:00 2001 From: Goffredo Marocchi Date: Fri, 12 Oct 2018 09:28:51 +0100 Subject: [PATCH 1/4] Added swift formatter, skipping unused check, and commit hooks --- .swiftlint.yml | 58 +++++++ .../LocalizeExample/Sources/AppDelegate.swift | 4 +- .../Sources/ViewController.swift | 10 +- Localize.swift => Sources/Localize.swift | 160 +++++++++--------- formatSwiftCode.sh | 28 +++ installModuleCommitHooks.sh | 5 + 6 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 .swiftlint.yml rename Localize.swift => Sources/Localize.swift (73%) create mode 100755 formatSwiftCode.sh create mode 100755 installModuleCommitHooks.sh diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..b15c1c4 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,58 @@ +disabled_rules: # rule identifiers to exclude from running + - colon + - control_statement + - identifier_name + - unused_optional_binding + - notification_center_detachment + - opening_brace +opt_in_rules: # some rules are only opt-in + - empty_count + - closure_spacing + - fatal_error_message + # Find all the available rules by running: + # swiftlint rules +included: # paths to include during linting. `--path` is ignored if present. + - Sources + - Example/LocalizeExample/Sources +excluded: # paths to ignore during linting. Takes precedence over `included`. + - Carthage + - Pods + +# configurable rules can be customized from this configuration file +# binary rules can set their severity level +force_cast: warning # implicitly +force_try: + severity: warning # explicitly +# rules that have both warning and error levels, can set just the warning level +# implicitly +line_length: 195 +# they can set both implicitly with an array +type_body_length: + - 350 # warning + - 450 # error +function_body_length: + - 60 # warning + - 120 # error +# or they can set both explicitly +file_length: + warning: 800 + error: 1400 +cyclomatic_complexity: + - 16 +# naming rules can set warnings/errors for min_length and max_length +# additionally they can set excluded names +type_name: + min_length: 3 # only warning + max_length: # warning and error + warning: 40 + error: 50 + excluded: iPhone # excluded via string +identifier_name: + min_length: # only min_length + error: 4 # only error + excluded: # excluded via string array + - id + - URL + - url + - GlobalAPIKey +reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) diff --git a/Example/LocalizeExample/Sources/AppDelegate.swift b/Example/LocalizeExample/Sources/AppDelegate.swift index afb844d..e260b2c 100644 --- a/Example/LocalizeExample/Sources/AppDelegate.swift +++ b/Example/LocalizeExample/Sources/AppDelegate.swift @@ -10,11 +10,9 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { return true } } - diff --git a/Example/LocalizeExample/Sources/ViewController.swift b/Example/LocalizeExample/Sources/ViewController.swift index 4d88dbd..7a34936 100644 --- a/Example/LocalizeExample/Sources/ViewController.swift +++ b/Example/LocalizeExample/Sources/ViewController.swift @@ -9,14 +9,12 @@ import UIKit class ViewController: UIViewController { - override func viewDidLoad() { super.viewDidLoad() - + // Here simulates their usage in code - let _ = NSLocalizedString("UntranslatedKey", comment: "") - let _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "") - let _ = NSLocalizedString("MissingKey", comment: "") + _ = NSLocalizedString("UntranslatedKey", comment: "") + _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "") + _ = NSLocalizedString("MissingKey", comment: "") } } - diff --git a/Localize.swift b/Sources/Localize.swift similarity index 73% rename from Localize.swift rename to Sources/Localize.swift index 81984d8..7a5c527 100755 --- a/Localize.swift +++ b/Sources/Localize.swift @@ -2,14 +2,12 @@ import Foundation - // WHAT // 1. Find Missing keys in other Localisation files // 2. Find potentially untranslated keys // 3. Find Duplicate keys // 4. Find Unused keys and generate script to delete them all at once - // MARK: Start Of Configurable Section /* @@ -18,9 +16,14 @@ import Foundation let enabled = true /* - Put your path here, example -> Resources/Localizations/Languages + You can skip unused keys detection + */ +let skipUnused = false + +/* + Put your path here, example -> Resources/Localizations/Languages or Resources */ -let relativeLocalizableFolders = "/Resources/Languages" +let relativeLocalizableFolders = "/Resources" /* This is the path of your source folder which will be used in searching @@ -59,7 +62,7 @@ let masterLanguage = "en" /* Sanitizing files will remove comments, empty lines and order your keys alphabetically. */ -let sanitizeFiles = false +let sanitizeFiles = true /* Determines if there are multiple localizations or not. @@ -69,16 +72,6 @@ let singleLanguage = false // MARK: End Of Configurable Section // MARK: - - - - - - - - - - - if enabled == false { print("Localization check cancelled") exit(000) @@ -105,54 +98,57 @@ func listSupportedLanguages() -> [String] { return sl } - let supportedLanguages = listSupportedLanguages() -var ignoredFromSameTranslation = [String:[String]]() +var ignoredFromSameTranslation = [String: [String]]() let path = FileManager.default.currentDirectoryPath + relativeLocalizableFolders var numberOfWarnings = 0 var numberOfErrors = 0 struct LocalizationFiles { var name = "" - var keyValue = [String:String]() - var linesNumbers = [String:Int]() - + var keyValue = [String: String]() + var linesNumbers = [String: Int]() + init(name: String) { self.name = name - process() + self.process() } - + mutating func process() { + print("process") if sanitizeFiles { - removeCommentsFromFile() - removeEmptyLinesFromFile() - sortLinesAlphabetically() + print("process... sanitizeFiles") + self.removeCommentsFromFile() + self.removeEmptyLinesFromFile() + self.sortLinesAlphabetically() } + print("process2") let location = singleLanguage ? "\(path)/Localizable.strings" : "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { - let lines = string.components(separatedBy: CharacterSet.newlines) - keyValue = [String:String]() + let lines = string.components(separatedBy: CharacterSet.newlines) + keyValue = [String: String]() let pattern = "\"(.*)\" = \"(.+)\";" let regex = try? NSRegularExpression(pattern: pattern, options: []) var ignoredTranslation = [String]() - + for (lineNumber, line) in lines.enumerated() { - let range = NSRange(location:0, length:(line as NSString).length) - - + let range = NSRange(location: 0, length: (line as NSString).length) + // Ignored pattern let ignoredPattern = "\"(.*)\" = \"(.+)\"; *\\/\\/ *ignore-same-translation-warning" let ignoredRegex = try? NSRegularExpression(pattern: ignoredPattern, options: []) - if let ignoredMatch = ignoredRegex?.firstMatch(in:line, - options: [], - range: range) { - let key = (line as NSString).substring(with: ignoredMatch.range(at:1)) + if let ignoredMatch = ignoredRegex?.firstMatch( + in: line, + options: [], + range: range + ) { + let key = (line as NSString).substring(with: ignoredMatch.range(at: 1)) ignoredTranslation.append(key) } if let firstMatch = regex?.firstMatch(in: line, options: [], range: range) { - let key = (line as NSString).substring(with: firstMatch.range(at:1)) - let value = (line as NSString).substring(with: firstMatch.range(at:2)) - if let _ = keyValue[key] { + let key = (line as NSString).substring(with: firstMatch.range(at: 1)) + let value = (line as NSString).substring(with: firstMatch.range(at: 2)) + if let _ = keyValue[key] { let str = "\(path)/\(name).lproj" + "/Localizable.strings:\(linesNumbers[key]!): " + "error: [Redundance] \"\(key)\" " @@ -160,8 +156,8 @@ struct LocalizationFiles { print(str) numberOfErrors += 1 } else { - keyValue[key] = value - linesNumbers[key] = lineNumber+1 + self.keyValue[key] = value + self.linesNumbers[key] = lineNumber + 1 } } } @@ -169,54 +165,55 @@ struct LocalizationFiles { ignoredFromSameTranslation[name] = ignoredTranslation } } - + func rebuildFileString(from lines: [String]) -> String { return lines.reduce("") { (r: String, s: String) -> String in - return (r == "") ? (r + s) : (r + "\n" + s) + (r == "") ? (r + s) : (r + "\n" + s) } } - + func removeEmptyLinesFromFile() { let location = "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { var lines = string.components(separatedBy: CharacterSet.newlines) lines = lines.filter { $0.trimmingCharacters(in: CharacterSet.whitespaces) != "" } let s = rebuildFileString(from: lines) - try? s.write(toFile:location, atomically:false, encoding:String.Encoding.utf8) + try? s.write(toFile: location, atomically: false, encoding: String.Encoding.utf8) } } - + func removeCommentsFromFile() { let location = "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { var lines = string.components(separatedBy: CharacterSet.newlines) lines = lines.filter { !$0.hasPrefix("//") } let s = rebuildFileString(from: lines) - try? s.write(toFile:location, atomically:false, encoding:String.Encoding.utf8) + try? s.write(toFile: location, atomically: false, encoding: String.Encoding.utf8) } } - + func sortLinesAlphabetically() { + print("Sanitising") let location = "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { let lines = string.components(separatedBy: CharacterSet.newlines) - + var s = "" - for (i,l) in sortAlphabetically(lines).enumerated() { + for (i, l) in self.sortAlphabetically(lines).enumerated() { s += l if (i != lines.count - 1) { s += "\n" } } - try? s.write(toFile:location, atomically:false, encoding:String.Encoding.utf8) + try? s.write(toFile: location, atomically: false, encoding: String.Encoding.utf8) } } - - func removeEmptyLinesFromLines(_ lines:[String]) -> [String] { + + func removeEmptyLinesFromLines(_ lines: [String]) -> [String] { return lines.filter { $0.trimmingCharacters(in: CharacterSet.whitespaces) != "" } } - - func sortAlphabetically(_ lines:[String]) -> [String] { + + func sortAlphabetically(_ lines: [String]) -> [String] { return lines.sorted() } } @@ -231,25 +228,27 @@ let localizationFiles = supportedLanguages // MARK: - Detect Unused Keys let sourcesPath = FileManager.default.currentDirectoryPath + relativeSourceFolder let fileManager = FileManager.default -let enumerator = fileManager.enumerator(atPath:sourcesPath) +let enumerator = fileManager.enumerator(atPath: sourcesPath) var localizedStrings = [String]() while let swiftFileLocation = enumerator?.nextObject() as? String { // checks the extension // TODO OBJC? - if swiftFileLocation.hasSuffix(".swift") || swiftFileLocation.hasSuffix(".m") || swiftFileLocation.hasSuffix(".mm") { + if swiftFileLocation.hasSuffix(".swift") || swiftFileLocation.hasSuffix(".m") || swiftFileLocation.hasSuffix(".mm") { let location = "\(sourcesPath)/\(swiftFileLocation)" if let string = try? String(contentsOfFile: location, encoding: .utf8) { for p in patterns { let regex = try? NSRegularExpression(pattern: p, options: []) - let range = NSRange(location:0, length:(string as NSString).length) //Obj c wa - regex?.enumerateMatches(in: string, - options: [], - range: range, - using: { (result, _, _) in - if let r = result { - let value = (string as NSString).substring(with:r.range(at:r.numberOfRanges-1)) - localizedStrings.append(value) - } - }) + let range = NSRange(location: 0, length: (string as NSString).length) //Obj c wa + regex?.enumerateMatches( + in: string, + options: [], + range: range, + using: { (result, _, _) in + if let r = result { + let value = (string as NSString).substring(with: r.range(at: r.numberOfRanges - 1)) + localizedStrings.append(value) + } + } + ) } } } @@ -263,24 +262,26 @@ let unused = masterKeys.subtracting(usedKeys).subtracting(ignored) // Here generate Xcode regex Find and replace script to remove dead keys all at once! var replaceCommand = "\"(" var counter = 0 -for v in unused { - var str = "\(path)/\(masterLocalizationfile.name).lproj/Localizable.strings:\(masterLocalizationfile.linesNumbers[v]!): " - str += "error: [Unused Key] \"\(v)\" is never used" - print(str) - numberOfErrors += 1 - if counter != 0 { - replaceCommand += "|" - } - replaceCommand += v - if counter == unused.count-1 { - replaceCommand += ")\" = \".*\";" + +if (!skipUnused) { + for v in unused { + var str = "\(path)/\(masterLocalizationfile.name).lproj/Localizable.strings:\(masterLocalizationfile.linesNumbers[v]!): " + str += "error: [Unused Key] \"\(v)\" is never used" + print(str) + numberOfErrors += 1 + if counter != 0 { + replaceCommand += "|" + } + replaceCommand += v + if counter == unused.count - 1 { + replaceCommand += ")\" = \".*\";" + } + counter += 1 } - counter += 1 } print(replaceCommand) - // MARK: - Compare each translation file against master (en) for file in localizationFiles { for k in masterLocalizationfile.keyValue.keys { @@ -310,4 +311,3 @@ print("Number of errors : \(numberOfErrors)") if numberOfErrors > 0 { exit(1) } - diff --git a/formatSwiftCode.sh b/formatSwiftCode.sh new file mode 100755 index 0000000..68bcf13 --- /dev/null +++ b/formatSwiftCode.sh @@ -0,0 +1,28 @@ +#!/bin/bash -e + +set -e + +function checkCommand { + commandName="${1}" + commandLocation="" + + if ! commandLocation="$(type -p "$commandName")" || [[ -z $commandLocation ]]; then + echo "Please install ${commandName}..." + exit 0 + fi +} + +checkCommand swiftlint +checkCommand swiftformat + +swiftformat Example/LocalizeExample/Sources Sources --cache ignore --indent 4 \ +--self insert \ +--wrapcollections beforefirst --wraparguments beforefirst \ +--comments ignore --commas inline \ +--disable blankLinesAroundMark,hoistPatternLet,redundantParens,redundantVoidReturnType,trailingClosures + +swiftlint autocorrect + +git add . + +echo "Formatting done..." diff --git a/installModuleCommitHooks.sh b/installModuleCommitHooks.sh new file mode 100755 index 0000000..4c48d88 --- /dev/null +++ b/installModuleCommitHooks.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e +hooksFolder=`git rev-parse --git-dir`/hooks + +echo "Installing hook in ${hooksFolder}" +cp -f formatSwiftCode.sh "${hooksFolder}"/pre-commit From 906c7877db9737afa73faa20d906966c2300b793 Mon Sep 17 00:00:00 2001 From: Goffredo Marocchi Date: Fri, 12 Oct 2018 09:35:55 +0100 Subject: [PATCH 2/4] Added Dangerfile Signed-off-by: Goffredo Marocchi --- Dangerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Dangerfile diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 0000000..698913d --- /dev/null +++ b/Dangerfile @@ -0,0 +1,7 @@ +swiftformat.additional_args = "Example/LocalizeExample/Sources Sources --cache ignore --indent 4 --self insert \ +--wrapcollections beforefirst --wraparguments beforefirst \ +--comments ignore --commas inline \ +--disable blankLinesAroundMark,hoistPatternLet,redundantParens,redundantVoidReturnType,trailingClosures" # optional +swiftformat.check_format(fail_on_error: true) + +swiftlint.lint_files From 2634bac6f8aee24b42dfd41077282a0a01299406 Mon Sep 17 00:00:00 2001 From: Goffredo Marocchi Date: Fri, 12 Oct 2018 09:52:13 +0100 Subject: [PATCH 3/4] Skip unused defaulted to true Signed-off-by: Goffredo Marocchi --- Sources/Localize.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Localize.swift b/Sources/Localize.swift index 7a5c527..2141fbc 100755 --- a/Sources/Localize.swift +++ b/Sources/Localize.swift @@ -18,7 +18,7 @@ let enabled = true /* You can skip unused keys detection */ -let skipUnused = false +let skipUnused = true /* Put your path here, example -> Resources/Localizations/Languages or Resources From 10976a6dec1fec31e60694dd5d3db1a0af9a9dd3 Mon Sep 17 00:00:00 2001 From: Goffredo Marocchi Date: Fri, 1 Feb 2019 00:18:52 +0000 Subject: [PATCH 4/4] Updated Dangerfile Signed-off-by: Goffredo Marocchi --- Dangerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dangerfile b/Dangerfile index 698913d..9e728b5 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,7 +1,7 @@ swiftformat.additional_args = "Example/LocalizeExample/Sources Sources --cache ignore --indent 4 --self insert \ --wrapcollections beforefirst --wraparguments beforefirst \ --comments ignore --commas inline \ ---disable blankLinesAroundMark,hoistPatternLet,redundantParens,redundantVoidReturnType,trailingClosures" # optional +--disable blankLinesAroundMark,hoistPatternLet,redundantParens,redundantVoidReturnType,trailingClosures,andOperator" # optional swiftformat.check_format(fail_on_error: true) swiftlint.lint_files