diff --git a/Scripts/DecryptExportedKey.swift b/Scripts/DecryptExportedKey.swift index d17e6ef2e..4e93ed3ff 100644 --- a/Scripts/DecryptExportedKey.swift +++ b/Scripts/DecryptExportedKey.swift @@ -1,4 +1,6 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import CryptoKit diff --git a/Scripts/EmojiGenerator.swift b/Scripts/EmojiGenerator.swift index 680d86e5d..0c991433b 100755 --- a/Scripts/EmojiGenerator.swift +++ b/Scripts/EmojiGenerator.swift @@ -1,5 +1,11 @@ #!/usr/bin/env xcrun --sdk macosx swift +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// This script is used to generate/update the set of Emoji used for reactions +// +// stringlint:disable + import Foundation // OWSAssertionError but for this script @@ -250,6 +256,7 @@ extension EmojiGenerator { // e.g. case grinning = "😀" writeBlock(fileName: "Emoji.swift") { fileHandle in fileHandle.writeLine("// swiftlint:disable all") + fileHandle.writeLine("// stringlint:disable") fileHandle.writeLine("") fileHandle.writeLine("/// A sorted representation of all available emoji") fileHandle.writeLine("enum Emoji: String, CaseIterable, Equatable {") @@ -306,6 +313,9 @@ extension EmojiGenerator { // if rawValue == "😀" { self.init(baseEmoji: .grinning, skinTones: nil) } // else if rawValue == "🦻🏻" { self.init(baseEmoji: .earWithHearingAid, skinTones: [.light]) writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in + fileHandle.writeLine("// swiftlint:disable all") + fileHandle.writeLine("// stringlint:disable") + fileHandle.writeLine("") fileHandle.writeLine("extension EmojiWithSkinTones {") fileHandle.indent { switch desiredStructure { @@ -372,6 +382,7 @@ extension EmojiGenerator { } } fileHandle.writeLine("}") + fileHandle.writeLine("// swiftlint:disable all") } } @@ -436,6 +447,9 @@ extension EmojiGenerator { static func writeSkinToneLookupFile(from emojiModel: EmojiModel) { writeBlock(fileName: "Emoji+SkinTones.swift") { fileHandle in + fileHandle.writeLine("// swiftlint:disable all") + fileHandle.writeLine("// stringlint:disable") + fileHandle.writeLine("") fileHandle.writeLine("extension Emoji {") fileHandle.indent { // SkinTone enum @@ -498,6 +512,7 @@ extension EmojiGenerator { fileHandle.writeLine("}") } fileHandle.writeLine("}") + fileHandle.writeLine("// swiftlint:disable all") } } @@ -514,6 +529,9 @@ extension EmojiGenerator { ] writeBlock(fileName: "Emoji+Category.swift") { fileHandle in + fileHandle.writeLine("// swiftlint:disable all") + fileHandle.writeLine("// stringlint:disable") + fileHandle.writeLine("") fileHandle.writeLine("extension Emoji {") fileHandle.indent { @@ -619,6 +637,7 @@ extension EmojiGenerator { fileHandle.writeLine("}") } fileHandle.writeLine("}") + fileHandle.writeLine("// swiftlint:disable all") } } @@ -626,6 +645,9 @@ extension EmojiGenerator { // Name lookup: Create a computed property mapping an Emoji enum element to the raw Emoji name string // e.g. case .grinning: return "GRINNING FACE" writeBlock(fileName: "Emoji+Name.swift") { fileHandle in + fileHandle.writeLine("// swiftlint:disable all") + fileHandle.writeLine("// stringlint:disable") + fileHandle.writeLine("") fileHandle.writeLine("extension Emoji {") fileHandle.indent { fileHandle.writeLine("var name: String {") @@ -639,6 +661,7 @@ extension EmojiGenerator { fileHandle.writeLine("}") } fileHandle.writeLine("}") + fileHandle.writeLine("// swiftlint:disable all") } } } diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index 910179348..0f273e227 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -1,264 +1,556 @@ #!/usr/bin/xcrun --sdk macosx swift -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. // // This script is based on https://github.com/ginowu7/CleanSwiftLocalizableExample the main difference // is canges to the localized usage regex +// +// stringlint:disable import Foundation -let fileManager = FileManager.default -let currentPath = ( - ProcessInfo.processInfo.environment["PROJECT_DIR"] ?? fileManager.currentDirectoryPath +extension ProjectState { + /// Adding `// stringlint:disable` to the top of a source file (before imports) or after a string will mean that file/line gets + /// ignored by this script (good for some things like the auto-generated emoji strings or debug strings) + static let lintSuppression: String = "stringlint:disable" + static let primaryLocalisationFile: String = "en" + static let validLocalisationSuffixes: Set = ["Localizable.strings"] + static let validSourceSuffixes: Set = [".swift", ".m"] + static let excludedPaths: Set = [ + "build/", // Files under the build folder (CI) + "Pods/", // The pods folder + "Protos/", // The protobuf files + ".xcassets/", // Asset bundles + ".app/", // App build directories + ".appex/", // Extension build directories + "tests/", // Exclude test directories + "_SharedTestUtilities/", // Exclude shared test directory + "external/" // External dependencies + ] + static let excludedPhrases: Set = [ "", " ", ",", ", ", "null" ] + static let excludedUnlocalisedStringLineMatching: Set = [ + .contains(ProjectState.lintSuppression), + .prefix("#import"), + .prefix("@available("), + .contains("fatalError("), + .contains("precondition("), + .contains("preconditionFailure("), + .contains("print("), + .contains("NSLog("), + .contains("SNLog("), + .contains("owsFailDebug("), + .contains("#imageLiteral(resourceName:"), + .contains("UIImage(named:"), + .contains("UIImage(systemName:"), + .contains("[UIImage imageNamed:"), + .contains("UIFont(name:"), + .contains(".accessibilityLabel ="), + .contains(".accessibilityIdentifier ="), + .contains("accessibilityIdentifier:"), + .contains("accessibilityLabel:"), + .contains("Accessibility(identifier:"), + .contains("Accessibility(label:"), + .containsAnd("identifier:", .previousLine(numEarlier: 1, .contains("Accessibility("))), + .containsAnd("label:", .previousLine(numEarlier: 1, .contains("Accessibility("))), + .containsAnd("label:", .previousLine(numEarlier: 2, .contains("Accessibility("))), + .contains("SQL("), + .regex(".*static var databaseTableName: String"), + .regex("Logger\\..*\\("), + .regex("OWSLogger\\..*\\("), + .regex("case .* = ") + ] +} + +// Execute the desired actions +let targetActions: Set = { + let args = CommandLine.arguments + + // The first argument is the file name + guard args.count > 1 else { return [.lintStrings] } + + return Set(args.suffix(from: 1).map { (ScriptAction(rawValue: $0) ?? .lintStrings) }) +}() + +print("------------ Searching Through Files ------------") +let projectState: ProjectState = ProjectState( + path: ( + ProcessInfo.processInfo.environment["PROJECT_DIR"] ?? + FileManager.default.currentDirectoryPath + ), + loadSourceFiles: targetActions.contains(.lintStrings) ) +print("------------ Found \(projectState.localizationFiles.count) Localization File(s) ------------") +targetActions.forEach { $0.perform(projectState: projectState) } -/// List of files in currentPath - recursive -var pathFiles: [String] = { - guard - let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator( - at: URL(fileURLWithPath: currentPath), - includingPropertiesForKeys: [.isDirectoryKey], - options: [.skipsHiddenFiles] - ), - let fileUrls: [URL] = enumerator.allObjects as? [URL] - else { fatalError("Could not locate files in path directory: \(currentPath)") } +// MARK: - ScriptAction + +enum ScriptAction: String { + case validateFilesCopied = "validate" + case lintStrings = "lint" - return fileUrls - .filter { - ((try? $0.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && // No directories - !$0.path.contains("build/") && // Exclude files under the build folder (CI) - !$0.path.contains("Pods/") && // Exclude files under the pods folder - !$0.path.contains(".xcassets") && // Exclude asset bundles - !$0.path.contains(".app/") && // Exclude files in the app build directories - !$0.path.contains(".appex/") && // Exclude files in the extension build directories - !$0.path.localizedCaseInsensitiveContains("tests/") && // Exclude files under test directories - !$0.path.localizedCaseInsensitiveContains("external/") && ( // Exclude files under external directories - // Only include relevant files - $0.path.hasSuffix("Localizable.strings") || - NSString(string: $0.path).pathExtension == "swift" || - NSString(string: $0.path).pathExtension == "m" - ) - } - .map { $0.path } -}() - - -/// List of localizable files - not including Localizable files in the Pods -var localizableFiles: [String] = { - return pathFiles.filter { $0.hasSuffix("Localizable.strings") } -}() - - -/// List of executable files -var executableFiles: [String] = { - return pathFiles.filter { - $0.hasSuffix(".swift") || - $0.hasSuffix(".m") - } -}() - -/// Reads contents in path -/// -/// - Parameter path: path of file -/// - Returns: content in file -func contents(atPath path: String) -> String { - guard let data = fileManager.contents(atPath: path), let content = String(data: data, encoding: .utf8) else { - fatalError("Could not read from path: \(path)") - } - - return content -} - -/// Returns a list of strings that match regex pattern from content -/// -/// - Parameters: -/// - pattern: regex pattern -/// - content: content to match -/// - Returns: list of results -func regexFor(_ pattern: String, content: String) -> [String] { - guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { - fatalError("Regex not formatted correctly: \(pattern)") - } - - let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: content.utf16.count)) - - return matches.map { - guard let range = Range($0.range(at: 0), in: content) else { - fatalError("Incorrect range match") - } - - return String(content[range]) - } -} - -func create() -> [LocalizationStringsFile] { - return localizableFiles.map(LocalizationStringsFile.init(path:)) -} - -/// -/// -/// - Returns: A list of LocalizationCodeFile - contains path of file and all keys in it -func localizedStringsInCode() -> [LocalizationCodeFile] { - return executableFiles.compactMap { - let content = contents(atPath: $0) - // Note: Need to exclude escaped quotation marks from strings - let matchesOld = regexFor("(?<=NSLocalizedString\\()\\s*\"(?!.*?%d)(.*?)\"", content: content) - let matchesNew = regexFor("\"(?!.*?%d)([^(\\\")]*?)\"(?=\\s*)(?=\\.localized)", content: content) - let allMatches = (matchesOld + matchesNew) - - return allMatches.isEmpty ? nil : LocalizationCodeFile(path: $0, keys: Set(allMatches)) - } -} - -/// Throws error if ALL localizable files does not have matching keys -/// -/// - Parameter files: list of localizable files to validate -func validateMatchKeys(_ files: [LocalizationStringsFile]) { - guard let base = files.first, files.count > 1 else { return } - - let files = Array(files.dropFirst()) - - files.forEach { - guard let extraKey = Set(base.keys).symmetricDifference($0.keys).first else { return } - let incorrectFile = $0.keys.contains(extraKey) ? $0 : base - printPretty("error: Found extra key: \(extraKey) in file: \(incorrectFile.path)") - } -} - -/// Throws error if localizable files are missing keys -/// -/// - Parameters: -/// - codeFiles: Array of LocalizationCodeFile -/// - localizationFiles: Array of LocalizableStringFiles -func validateMissingKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) { - guard let baseFile = localizationFiles.first else { - fatalError("Could not locate base localization file") - } - - let baseKeys = Set(baseFile.keys) - - codeFiles.forEach { - let extraKeys = $0.keys.subtracting(baseKeys) - if !extraKeys.isEmpty { - printPretty("error: Found keys in code missing in strings file: \(extraKeys) from \($0.path)") - } - } -} - -/// Throws warning if keys exist in localizable file but are not being used -/// -/// - Parameters: -/// - codeFiles: Array of LocalizationCodeFile -/// - localizationFiles: Array of LocalizableStringFiles -func validateDeadKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) { - guard let baseFile = localizationFiles.first else { - fatalError("Could not locate base localization file") - } - - let baseKeys: Set = Set(baseFile.keys) - let allCodeFileKeys: [String] = codeFiles.flatMap { $0.keys } - let deadKeys: [String] = Array(baseKeys.subtracting(allCodeFileKeys)) - .sorted() - .map { $0.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) } - - if !deadKeys.isEmpty { - printPretty("warning: \(deadKeys) - Suggest cleaning dead keys") - } -} - -protocol Pathable { - var path: String { get } -} - -struct LocalizationStringsFile: Pathable { - let path: String - let kv: [String: String] - let duplicates: [(key: String, path: String)] - - var keys: [String] { - return Array(kv.keys) - } - - init(path: String) { - let result = ContentParser.parse(path) - - self.path = path - self.kv = result.kv - self.duplicates = result.duplicates - } - - /// Writes back to localizable file with sorted keys and removed whitespaces and new lines - func cleanWrite() { - print("------------ Sort and remove whitespaces: \(path) ------------") - let content = kv.keys.sorted().map { "\($0) = \(kv[$0]!);" }.joined(separator: "\n") - try! content.write(toFile: path, atomically: true, encoding: .utf8) - } - -} - -struct LocalizationCodeFile: Pathable { - let path: String - let keys: Set -} - -struct ContentParser { - - /// Parses contents of a file to localizable keys and values - Throws error if localizable file have duplicated keys - /// - /// - Parameter path: Localizable file paths - /// - Returns: localizable key and value for content at path - static func parse(_ path: String) -> (kv: [String: String], duplicates: [(key: String, path: String)]) { - let content = contents(atPath: path) - let trimmed = content - .replacingOccurrences(of: "\n+", with: "", options: .regularExpression, range: nil) - .trimmingCharacters(in: .whitespacesAndNewlines) - let keys = regexFor("\"([^\"]*?)\"(?= =)", content: trimmed) - let values = regexFor("(?<== )\"(.*?)\"(?=;)", content: trimmed) - - if keys.count != values.count { - fatalError("Error parsing contents: Make sure all keys and values are in correct format (this could be due to extra spaces between keys and values)") - } - - var duplicates: [(key: String, path: String)] = [] - let kv: [String: String] = zip(keys, values) - .reduce(into: [:]) { results, keyValue in - guard results[keyValue.0] == nil else { - duplicates.append((keyValue.0, path)) - return + func perform(projectState: ProjectState) { + // Perform the action + switch self { + case .validateFilesCopied: + print("------------ Checking Copied Files ------------") + guard + let builtProductsPath: String = ProcessInfo.processInfo.environment["BUILT_PRODUCTS_DIR"], + let productName: String = ProcessInfo.processInfo.environment["FULL_PRODUCT_NAME"], + let enumerator: FileManager.DirectoryEnumerator = FileManager.default.enumerator( + at: URL(fileURLWithPath: "\(builtProductsPath)/\(productName)"), + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ), + let fileUrls: [URL] = enumerator.allObjects as? [URL] + else { return Output.error("Could not retrieve list of files within built product") } + + let localizationFiles: Set = Set(fileUrls + .filter { $0.path.hasSuffix(".lproj") } + .map { $0.lastPathComponent.replacingOccurrences(of: ".lproj", with: "") }) + let missingFiles: Set = Set(projectState.localizationFiles + .map { $0.name }) + .subtracting(localizationFiles) + + guard missingFiles.isEmpty else { + return Output.error("Translations missing from \(productName): \(missingFiles.joined(separator: ", "))") + } + break + + case .lintStrings: + guard !projectState.localizationFiles.isEmpty else { + return print("------------ Nothing to lint ------------") } - results[keyValue.0] = keyValue.1 - } + print("------------ Processing \(projectState.localizationFiles.count) Localization File(s) ------------") + + // Add warnings for any duplicate keys + projectState.localizationFiles.forEach { file in + // Show errors for any duplicates + file.duplicates.forEach { phrase, original in Output.duplicate(phrase, original: original) } + + // Show warnings for any phrases missing from the file + let allKeys: Set = Set(file.keyPhrase.keys) + let missingKeysFromOtherFiles: [String: [String]] = projectState.localizationFiles.reduce(into: [:]) { result, otherFile in + guard otherFile.path != file.path else { return } + + let missingKeys: Set = Set(otherFile.keyPhrase.keys) + .subtracting(allKeys) + + missingKeys.forEach { missingKey in + result[missingKey] = ((result[missingKey] ?? []) + [otherFile.name]) + } + } + + missingKeysFromOtherFiles.forEach { missingKey, namesOfFilesItWasFound in + Output.warning(file, "Phrase '\(missingKey)' is missing (found in: \(namesOfFilesItWasFound.joined(separator: ", ")))") + } + } + + // Process the source code + print("------------ Processing \(projectState.sourceFiles.count) Source File(s) ------------") + let allKeys: Set = Set(projectState.primaryLocalizationFile.keyPhrase.keys) + + projectState.sourceFiles.forEach { file in + // Add logs for unlocalised strings + file.unlocalizedPhrases.forEach { phrase in + Output.warning(phrase, "Found unlocalized string '\(phrase.key)'") + } + + // Add errors for missing localised strings + let missingKeys: Set = Set(file.keyPhrase.keys).subtracting(allKeys) + missingKeys.forEach { key in + switch file.keyPhrase[key] { + case .some(let phrase): Output.error(phrase, "Localized phrase '\(key)' missing from strings files") + case .none: Output.error(file, "Localized phrase '\(key)' missing from strings files") + } + } + } + break + } - return (kv, duplicates) + print("------------ Complete ------------") } } -func printPretty(_ string: String) { - print(string.replacingOccurrences(of: "\\", with: "")) +// MARK: - Functionality + +enum Regex { + /// Returns a list of strings that match regex pattern from content + /// + /// - Parameters: + /// - pattern: regex pattern + /// - content: content to match + /// - Returns: list of results + static func matches(_ pattern: String, content: String) -> [String] { + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + fatalError("Regex not formatted correctly: \(pattern)") + } + + let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: content.utf16.count)) + + return matches.map { + guard let range = Range($0.range(at: 0), in: content) else { + fatalError("Incorrect range match") + } + + return String(content[range]) + } + } } -// MARK: - Processing +// MARK: - Output -let stringFiles: [LocalizationStringsFile] = create() - -if !stringFiles.isEmpty { - print("------------ Found \(stringFiles.count) file(s) - checking for duplicate, extra, missing and dead keys ------------") +enum Output { + static func error(_ error: String) { + print("error: \(error)") + } - stringFiles.forEach { file in - file.duplicates.forEach { key, path in - printPretty("error: Found duplicate key: \(key) in file: \(path)") + static func error(_ location: Locatable, _ error: String) { + print("\(location.location): error: \(error)") + } + + static func warning(_ location: Locatable, _ warning: String) { + print("\(location.location): warning: \(warning)") + } + + static func duplicate( + _ duplicate: KeyedLocatable, + original: KeyedLocatable + ) { + print("\(duplicate.location): error: duplicate key '\(original.key)'") + + // Looks like the `note:` doesn't work the same as when XCode does it unfortunately so we can't + // currently include the reference to the original entry + // print("\(original.location): note: previously found here") + } +} + +// MARK: - ProjectState + +struct ProjectState { + let primaryLocalizationFile: LocalizationStringsFile + let localizationFiles: [LocalizationStringsFile] + let sourceFiles: [SourceFile] + + init(path: String, loadSourceFiles: Bool) { + guard + let enumerator: FileManager.DirectoryEnumerator = FileManager.default.enumerator( + at: URL(fileURLWithPath: path), + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ), + let fileUrls: [URL] = enumerator.allObjects as? [URL] + else { fatalError("Could not locate files in path directory: \(path)") } + + // Get a list of valid URLs + let lowerCaseExcludedPaths: Set = Set(ProjectState.excludedPaths.map { $0.lowercased() }) + let validFileUrls: [URL] = fileUrls.filter { fileUrl in + ((try? fileUrl.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && + !lowerCaseExcludedPaths.contains { fileUrl.path.lowercased().contains($0) } + } + + // Localization files + let targetFileSuffixes: Set = Set(ProjectState.validLocalisationSuffixes.map { $0.lowercased() }) + self.localizationFiles = validFileUrls + .filter { fileUrl in targetFileSuffixes.contains { fileUrl.path.lowercased().contains($0) } } + .map { LocalizationStringsFile(path: $0.path) } + + guard let primaryLocalizationFile: LocalizationStringsFile = self.localizationFiles.first(where: { $0.name == ProjectState.primaryLocalisationFile }) else { + fatalError("Could not locate primary localization file: \(ProjectState.primaryLocalisationFile)") + } + self.primaryLocalizationFile = primaryLocalizationFile + + guard loadSourceFiles else { + self.sourceFiles = [] + return + } + + // Source files + let lowerCaseSourceSuffixes: Set = Set(ProjectState.validSourceSuffixes.map { $0.lowercased() }) + self.sourceFiles = validFileUrls + .filter { fileUrl in lowerCaseSourceSuffixes.contains(".\(fileUrl.pathExtension)") } + .compactMap { SourceFile(path: $0.path) } + } +} + +protocol Locatable { + var location: String { get } +} + +protocol KeyedLocatable: Locatable { + var key: String { get } +} + +extension ProjectState { + // MARK: - LocalizationStringsFile + + struct LocalizationStringsFile: Locatable { + struct Phrase: KeyedLocatable { + let key: String + let value: String + let filePath: String + let lineNumber: Int + + var location: String { "\(filePath):\(lineNumber)" } + } + + let name: String + let path: String + let keyPhrase: [String: Phrase] + let duplicates: [(Phrase, original: Phrase)] + + var location: String { path } + + init(path: String) { + let result = LocalizationStringsFile.parse(path) + + self.name = (path + .replacingOccurrences(of: "/Localizable.strings", with: "") + .replacingOccurrences(of: ".lproj", with: "") + .components(separatedBy: "/") + .last ?? "Unknown") + self.path = path + self.keyPhrase = result.keyPhrase + self.duplicates = result.duplicates + } + + static func parse(_ path: String) -> (keyPhrase: [String: Phrase], duplicates: [(Phrase, original: Phrase)]) { + guard + let data: Data = FileManager.default.contents(atPath: path), + let content: String = String(data: data, encoding: .utf8) + else { fatalError("Could not read from path: \(path)") } + + let lines: [String] = content.components(separatedBy: .newlines) + var duplicates: [(Phrase, original: Phrase)] = [] + var keyPhrase: [String: Phrase] = [:] + + lines.enumerated().forEach { lineNumber, line in + guard + let key: String = Regex.matches("\"([^\"]*?)\"(?= =)", content: line).first, + let value: String = Regex.matches("(?<== )\"(.*?)\"(?=;)", content: line).first + else { return } + + // Remove the quotation marks around the key + let trimmedKey: String = String(key + .prefix(upTo: key.index(before: key.endIndex)) + .suffix(from: key.index(after: key.startIndex))) + + // Files are 1-indexed but arrays are 0-indexed so add 1 to the lineNumber + let result: Phrase = Phrase( + key: trimmedKey, + value: value, + filePath: path, + lineNumber: (lineNumber + 1) + ) + + switch keyPhrase[trimmedKey] { + case .some(let original): duplicates.append((result, original)) + case .none: keyPhrase[trimmedKey] = result + } + } + + return (keyPhrase, duplicates) } } - validateMatchKeys(stringFiles) - - // Note: Uncomment the below file to clean out all comments from the localizable file (we don't want this because comments make it readable...) - // stringFiles.forEach { $0.cleanWrite() } - - let codeFiles: [LocalizationCodeFile] = localizedStringsInCode() - validateMissingKeys(codeFiles, localizationFiles: stringFiles) - validateDeadKeys(codeFiles, localizationFiles: stringFiles) + // MARK: - SourceFile + + struct SourceFile: Locatable { + struct Phrase: KeyedLocatable { + let term: String + let filePath: String + let lineNumber: Int + + var key: String { term } + var location: String { "\(filePath):\(lineNumber)" } + } + + let path: String + let keyPhrase: [String: Phrase] + let unlocalizedKeyPhrase: [String: Phrase] + let phrases: [Phrase] + let unlocalizedPhrases: [Phrase] + + var location: String { path } + + init?(path: String) { + guard let result = SourceFile.parse(path) else { return nil } + + self.path = path + self.keyPhrase = result.keyPhrase + self.unlocalizedKeyPhrase = result.unlocalizedKeyPhrase + self.phrases = result.phrases + self.unlocalizedPhrases = result.unlocalizedPhrases + } + + static func parse(_ path: String) -> (keyPhrase: [String: Phrase], phrases: [Phrase], unlocalizedKeyPhrase: [String: Phrase], unlocalizedPhrases: [Phrase])? { + guard + let data: Data = FileManager.default.contents(atPath: path), + let content: String = String(data: data, encoding: .utf8) + else { fatalError("Could not read from path: \(path)") } + + // If the file has the lint supression before the first import then ignore the file + let preImportContent: String = (content.components(separatedBy: "import").first ?? "") + + guard !preImportContent.contains(ProjectState.lintSuppression) else { + print("Explicitly ignoring \(path)") + return nil + } + + // Otherwise continue and process the file + let lines: [String] = content.components(separatedBy: .newlines) + var keyPhrase: [String: Phrase] = [:] + var unlocalizedKeyPhrase: [String: Phrase] = [:] + var phrases: [Phrase] = [] + var unlocalizedPhrases: [Phrase] = [] + + lines.enumerated().forEach { lineNumber, line in + let trimmedLine: String = line.trimmingCharacters(in: .whitespacesAndNewlines) + + // Ignore the line if it doesn't contain a quotation character (optimisation), it's + // been suppressed or it's explicitly excluded due to the rules at the top of the file + guard + trimmedLine.contains("\"") && + !ProjectState.excludedUnlocalisedStringLineMatching + .contains(where: { $0.matches(trimmedLine, lineNumber, lines) }) + else { return } + + // Split line based on commented out content and exclude the comment from the linting + let commentMatches: [String] = Regex.matches( + "//[^\\\"]*(?:\\\"[^\\\"]*\\\"[^\\\"]*)*", + content: line + ) + let targetLine: String = (commentMatches.isEmpty ? line : + line.components(separatedBy: commentMatches[0])[0] + ) + + // Use regex to find `NSLocalizedString("", "")`, `"".localised()` and any other `""` + // values in the source code + // + // Note: It's more complex because we need to exclude escaped quotation marks from + // strings and also want to ignore any strings that have been commented out, Swift + // also doesn't support "lookbehind" in regex so we can use that approach + var isUnlocalized: Bool = false + var allMatches: Set = Set( + Regex + .matches( + "NSLocalizedString\\(@{0,1}\\\"[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*(?:\\\")", + content: targetLine + ) + .map { match in + match + .removingPrefixIfPresent("NSLocalizedString(@\"") + .removingPrefixIfPresent("NSLocalizedString(\"") + .removingSuffixIfPresent("\")") + .removingSuffixIfPresent("\"") + } + ) + + // If we didn't get any matches for the standard `NSLocalizedString` then try our + // custom extension `"".localized()` + if allMatches.isEmpty { + allMatches = allMatches.union(Set( + Regex + .matches( + "\\\"[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*\\\"\\.localized", + content: targetLine + ) + .map { match in + match + .removingPrefixIfPresent("\"") + .removingSuffixIfPresent("\".localized") + } + )) + } + + /// If we still don't have any matches then try to match any strings as unlocalized strings (handling + /// nested `"Test\"string\" value"`, empty strings and strings only composed of quotes `"""""""`) + /// + /// **Note:** While it'd be nice to have the regex automatically exclude the quotes doing so makes it _far_ less + /// efficient (approx. by a factor of 8 times) so we remove those ourselves) + if allMatches.isEmpty { + // Find strings which are just not localised + let potentialUnlocalizedStrings: [String] = Regex + .matches("\\\"[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*(?:\\\")", content: targetLine) + // Remove the leading and trailing quotation marks + .map { $0.removingPrefixIfPresent("\"").removingSuffixIfPresent("\"") } + // Remove any empty strings + .filter { !$0.isEmpty } + // Remove any string conversations (ie. `.map { "\($0)" }` + .filter { value in !value.hasPrefix("\\(") || !value.hasSuffix(")") } + + allMatches = allMatches.union(Set(potentialUnlocalizedStrings)) + isUnlocalized = true + } + + // Remove any excluded phrases from the matches + allMatches = allMatches.subtracting(ProjectState.excludedPhrases.map { "\($0)" }) + + allMatches.forEach { match in + // Files are 1-indexed but arrays are 0-indexed so add 1 to the lineNumber + let result: Phrase = Phrase( + term: match, + filePath: path, + lineNumber: (lineNumber + 1) + ) + + if !isUnlocalized { + keyPhrase[match] = result + phrases.append(result) + } + else { + unlocalizedKeyPhrase[match] = result + unlocalizedPhrases.append(result) + } + } + } + + return (keyPhrase, phrases, unlocalizedKeyPhrase, unlocalizedPhrases) + } + } } -print("------------ Complete ------------") +indirect enum MatchType: Hashable { + case prefix(String) + case contains(String) + case containsAnd(String, MatchType) + case regex(String) + case previousLine(numEarlier: Int, MatchType) + + func matches(_ value: String, _ index: Int, _ lines: [String]) -> Bool { + switch self { + case .prefix(let prefix): + return value + .trimmingCharacters(in: .whitespacesAndNewlines) + .hasPrefix(prefix) + + case .contains(let other): return value.contains(other) + case .containsAnd(let other, let otherMatch): + guard value.contains(other) else { return false } + + return otherMatch.matches(value, index, lines) + + case .regex(let regex): return !Regex.matches(regex, content: value).isEmpty + + case .previousLine(let numEarlier, let type): + guard index >= numEarlier else { return false } + + let targetIndex: Int = (index - numEarlier) + return type.matches(lines[targetIndex], targetIndex, lines) + } + } +} + +extension String { + func removingPrefixIfPresent(_ value: String) -> String { + guard hasPrefix(value) else { return self } + + return String(self.suffix(from: self.index(self.startIndex, offsetBy: value.count))) + } + + func removingSuffixIfPresent(_ value: String) -> String { + guard hasSuffix(value) else { return self } + + return String(self.prefix(upTo: self.index(self.endIndex, offsetBy: -value.count))) + } +} diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index d6b5cc95e..af3a72fbe 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -802,6 +802,11 @@ FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; }; FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; }; FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; }; + FDC498B72AC15F7D00EDD897 /* AppNotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */; }; + FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */; }; + FDC498BB2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498BA2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift */; }; + FDC498BE2AC1732E00EDD897 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD9401A32ABD04AC003A4834 /* Localizable.strings */; }; + FDC498C22AC17BFC00EDD897 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD9401A32ABD04AC003A4834 /* Localizable.strings */; }; FDC6D6F32860607300B04575 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7542807C4BB004C14C5 /* Environment.swift */; }; FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC6D75F2862B3F600B04575 /* Dependencies.swift */; }; FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */; }; @@ -1940,6 +1945,9 @@ FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = ""; }; FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = ""; }; FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = ""; }; + FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationCategory.swift; sourceTree = ""; }; + FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationAction.swift; sourceTree = ""; }; + FDC498BA2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationUserInfoKey.swift; sourceTree = ""; }; FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = ""; }; FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = ""; }; FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = ""; }; @@ -3102,6 +3110,7 @@ C36096BB25AD1BBB008B62B2 /* Notifications */ = { isa = PBXGroup; children = ( + FDC498B52AC15F6D00EDD897 /* Types */, 4539B5851F79348F007141FF /* PushRegistrationManager.swift */, 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */, 451A13B01E13DED2000A50FD /* AppNotifications.swift */, @@ -4303,6 +4312,16 @@ path = _TestUtilities; sourceTree = ""; }; + FDC498B52AC15F6D00EDD897 /* Types */ = { + isa = PBXGroup; + children = ( + FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */, + FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */, + FDC498BA2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift */, + ); + path = Types; + sourceTree = ""; + }; FDDC08F029A300D500BF9681 /* Utilities */ = { isa = PBXGroup; children = ( @@ -4527,6 +4546,7 @@ 453518641FC635DD00210559 /* Sources */, 453518651FC635DD00210559 /* Frameworks */, 453518661FC635DD00210559 /* Resources */, + FDC498C02AC1774500EDD897 /* Ensure Localizable.strings included */, ); buildRules = ( ); @@ -4550,6 +4570,7 @@ 7BC01A37241F40AB00BC7C55 /* Sources */, 7BC01A38241F40AB00BC7C55 /* Frameworks */, 7BC01A39241F40AB00BC7C55 /* Resources */, + FDC498C12AC1775400EDD897 /* Ensure Localizable.strings included */, ); buildRules = ( ); @@ -4672,6 +4693,7 @@ 453518771FC635DD00210559 /* Embed Foundation Extensions */, 4535189F1FC63DBF00210559 /* Embed Frameworks */, FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */, + FDC498BF2AC1747900EDD897 /* Ensure Localizable.strings included */, 90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */, ); buildRules = ( @@ -4964,6 +4986,7 @@ buildActionMask = 2147483647; files = ( 4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */, + FDC498C22AC17BFC00EDD897 /* Localizable.strings in Resources */, B8D07406265C683A00F77E07 /* ElegantIcons.ttf in Resources */, 3478504C1FD7496D007B8332 /* Images.xcassets in Resources */, ); @@ -4973,6 +4996,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + FDC498BE2AC1732E00EDD897 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5429,6 +5453,66 @@ shellScript = "\"${SRCROOT}/Scripts/build_libSession_util.sh\"\n"; showEnvVarsInLog = 0; }; + FDC498BF2AC1747900EDD897 /* Ensure Localizable.strings included */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Ensure Localizable.strings included"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" validate\n"; + showEnvVarsInLog = 0; + }; + FDC498C02AC1774500EDD897 /* Ensure Localizable.strings included */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Ensure Localizable.strings included"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" validate\n"; + showEnvVarsInLog = 0; + }; + FDC498C12AC1775400EDD897 /* Ensure Localizable.strings included */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Ensure Localizable.strings included"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" validate\n"; + showEnvVarsInLog = 0; + }; FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -5468,7 +5552,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\"\n"; + shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" lint\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -6083,6 +6167,7 @@ FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */, FD37E9DD28A384EB003AE748 /* PrimaryColorSelectionView.swift in Sources */, B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */, + FDC498B72AC15F7D00EDD897 /* AppNotificationCategory.swift in Sources */, FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */, @@ -6100,6 +6185,7 @@ 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */, FD71163F28E2C82C00B47552 /* SessionHeaderView.swift in Sources */, B877E24226CA12910007970A /* CallVC.swift in Sources */, + FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */, 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, FD716E7128505E5200C96BF4 /* MessageRequestsCell.swift in Sources */, @@ -6161,6 +6247,7 @@ 7B9F71D72853100A006DFE7B /* Emoji+Available.swift in Sources */, FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */, 7B9F71D32852EEE2006DFE7B /* Emoji.swift in Sources */, + FDC498BB2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift in Sources */, C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, @@ -6507,7 +6594,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 425; + CURRENT_PROJECT_VERSION = 426; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6579,7 +6666,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 425; + CURRENT_PROJECT_VERSION = 426; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6644,7 +6731,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 425; + CURRENT_PROJECT_VERSION = 426; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6718,7 +6805,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 425; + CURRENT_PROJECT_VERSION = 426; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7678,7 +7765,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 425; + CURRENT_PROJECT_VERSION = 426; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7749,7 +7836,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 425; + CURRENT_PROJECT_VERSION = 426; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Backups/OWSBackupSettingsViewController.m b/Session/Backups/OWSBackupSettingsViewController.m deleted file mode 100644 index b65b2c47a..000000000 --- a/Session/Backups/OWSBackupSettingsViewController.m +++ /dev/null @@ -1,213 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSBackupSettingsViewController.h" -#import "OWSBackup.h" -#import "Session-Swift.h" - -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSBackupSettingsViewController () - -@property (nonatomic, nullable) NSError *iCloudError; - -@end - -#pragma mark - - -@implementation OWSBackupSettingsViewController - -#pragma mark - Dependencies - -- (OWSBackup *)backup -{ - OWSAssertDebug(AppEnvironment.shared.backup); - - return AppEnvironment.shared.backup; -} - -#pragma mark - - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.title = NSLocalizedString(@"SETTINGS_BACKUP", @"Label for the backup view in app settings."); - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(backupStateDidChange:) - name:NSNotificationNameBackupStateDidChange - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; - - [self updateTableContents]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - [self updateTableContents]; - [self updateICloudStatus]; -} - -- (void)updateICloudStatus -{ - __weak OWSBackupSettingsViewController *weakSelf = self; - [[self.backup ensureCloudKitAccess] - .then(^{ - OWSAssertIsOnMainThread(); - - weakSelf.iCloudError = nil; - [weakSelf updateTableContents]; - }) - .catch(^(NSError *error) { - OWSAssertIsOnMainThread(); - - weakSelf.iCloudError = error; - [weakSelf updateTableContents]; - }) retainUntilComplete]; -} - -#pragma mark - Table Contents - -- (void)updateTableContents -{ - OWSTableContents *contents = [OWSTableContents new]; - - BOOL isBackupEnabled = [OWSBackup.sharedManager isBackupEnabled]; - - if (self.iCloudError) { - OWSTableSection *iCloudSection = [OWSTableSection new]; - iCloudSection.headerTitle = NSLocalizedString( - @"SETTINGS_BACKUP_ICLOUD_STATUS", @"Label for iCloud status row in the in the backup settings view."); - [iCloudSection - addItem:[OWSTableItem - longDisclosureItemWithText:[OWSBackupAPI errorMessageForCloudKitAccessError:self.iCloudError] - actionBlock:^{ - [[UIApplication sharedApplication] - openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - [contents addSection:iCloudSection]; - } - - // TODO: This UI is temporary. - // Enabling backup will involve entering and registering a PIN. - OWSTableSection *enableSection = [OWSTableSection new]; - enableSection.headerTitle = NSLocalizedString(@"SETTINGS_BACKUP", @"Label for the backup view in app settings."); - [enableSection - addItem:[OWSTableItem switchItemWithText: - NSLocalizedString(@"SETTINGS_BACKUP_ENABLING_SWITCH", - @"Label for switch in settings that controls whether or not backup is enabled.") - isOnBlock:^{ - return [OWSBackup.sharedManager isBackupEnabled]; - } - target:self - selector:@selector(isBackupEnabledDidChange:)]]; - [contents addSection:enableSection]; - - if (isBackupEnabled) { - // TODO: This UI is temporary. - // Enabling backup will involve entering and registering a PIN. - OWSTableSection *progressSection = [OWSTableSection new]; - [progressSection - addItem:[OWSTableItem - labelItemWithText:NSLocalizedString(@"SETTINGS_BACKUP_STATUS", - @"Label for backup status row in the in the backup settings view.") - accessoryText:NSStringForBackupExportState(OWSBackup.sharedManager.backupExportState)]]; - if (OWSBackup.sharedManager.backupExportState == OWSBackupState_InProgress) { - if (OWSBackup.sharedManager.backupExportDescription) { - [progressSection - addItem:[OWSTableItem - labelItemWithText:NSLocalizedString(@"SETTINGS_BACKUP_PHASE", - @"Label for phase row in the in the backup settings view.") - accessoryText:OWSBackup.sharedManager.backupExportDescription]]; - if (OWSBackup.sharedManager.backupExportProgress) { - NSUInteger progressPercent - = (NSUInteger)round(OWSBackup.sharedManager.backupExportProgress.floatValue * 100); - NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setNumberStyle:NSNumberFormatterPercentStyle]; - [numberFormatter setMaximumFractionDigits:0]; - [numberFormatter setMultiplier:@1]; - NSString *progressString = [numberFormatter stringFromNumber:@(progressPercent)]; - [progressSection - addItem:[OWSTableItem - labelItemWithText:NSLocalizedString(@"SETTINGS_BACKUP_PROGRESS", - @"Label for phase row in the in the backup settings view.") - accessoryText:progressString]]; - } - } - } - - switch (OWSBackup.sharedManager.backupExportState) { - case OWSBackupState_Idle: - case OWSBackupState_Failed: - case OWSBackupState_Succeeded: - [progressSection - addItem:[OWSTableItem disclosureItemWithText: - NSLocalizedString(@"SETTINGS_BACKUP_BACKUP_NOW", - @"Label for 'backup now' button in the backup settings view.") - actionBlock:^{ - [OWSBackup.sharedManager tryToExportBackup]; - }]]; - break; - case OWSBackupState_InProgress: - [progressSection - addItem:[OWSTableItem disclosureItemWithText: - NSLocalizedString(@"SETTINGS_BACKUP_CANCEL_BACKUP", - @"Label for 'cancel backup' button in the backup settings view.") - actionBlock:^{ - [OWSBackup.sharedManager cancelExportBackup]; - }]]; - break; - } - - [contents addSection:progressSection]; - } - - self.contents = contents; -} - -- (void)isBackupEnabledDidChange:(UISwitch *)sender -{ - [OWSBackup.sharedManager setIsBackupEnabled:sender.isOn]; - - [self updateTableContents]; -} - -#pragma mark - Events - -- (void)backupStateDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self updateTableContents]; -} - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self updateICloudStatus]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Emoji/Emoji+Category.swift b/Session/Emoji/Emoji+Category.swift index 3ef0a93d3..ed3d9c6bc 100644 --- a/Session/Emoji/Emoji+Category.swift +++ b/Session/Emoji/Emoji+Category.swift @@ -1,6 +1,9 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. +// swiftlint:disable all +// stringlint:disable + extension Emoji { enum Category: String, CaseIterable, Equatable { case smileysAndPeople = "Smileys & People" @@ -3816,3 +3819,4 @@ extension Emoji { } } } +// swiftlint:disable all diff --git a/Session/Emoji/Emoji+Name.swift b/Session/Emoji/Emoji+Name.swift index 123bac051..2ddb050ad 100644 --- a/Session/Emoji/Emoji+Name.swift +++ b/Session/Emoji/Emoji+Name.swift @@ -1,6 +1,9 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. +// swiftlint:disable all +// stringlint:disable + extension Emoji { var name: String { switch self { @@ -1882,3 +1885,4 @@ extension Emoji { } } } +// swiftlint:disable all diff --git a/Session/Emoji/Emoji+SkinTones.swift b/Session/Emoji/Emoji+SkinTones.swift index facfd6d44..f1fb18434 100644 --- a/Session/Emoji/Emoji+SkinTones.swift +++ b/Session/Emoji/Emoji+SkinTones.swift @@ -1,6 +1,9 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. +// swiftlint:disable all +// stringlint:disable + extension Emoji { enum SkinTone: String, CaseIterable, Equatable { case light = "🏻" @@ -2738,3 +2741,4 @@ extension Emoji { } } } +// swiftlint:disable all diff --git a/Session/Emoji/Emoji.swift b/Session/Emoji/Emoji.swift index 15cda7df5..aa8352ca2 100644 --- a/Session/Emoji/Emoji.swift +++ b/Session/Emoji/Emoji.swift @@ -2,6 +2,7 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. // swiftlint:disable all +// stringlint:disable /// A sorted representation of all available emoji enum Emoji: String, CaseIterable, Equatable { diff --git a/Session/Emoji/EmojiWithSkinTones+String.swift b/Session/Emoji/EmojiWithSkinTones+String.swift index c57a6cba3..e1b1c84da 100644 --- a/Session/Emoji/EmojiWithSkinTones+String.swift +++ b/Session/Emoji/EmojiWithSkinTones+String.swift @@ -1,6 +1,9 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. +// swiftlint:disable all +// stringlint:disable + extension EmojiWithSkinTones { init?(rawValue: String) { guard rawValue.isSingleEmoji else { return nil } @@ -4726,3 +4729,4 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } } +// swiftlint:disable all diff --git a/Session/Emoji/EmojiWithSkinTones.swift b/Session/Emoji/EmojiWithSkinTones.swift index 293a9c9c2..1c703014f 100644 --- a/Session/Emoji/EmojiWithSkinTones.swift +++ b/Session/Emoji/EmojiWithSkinTones.swift @@ -64,7 +64,7 @@ extension Emoji { guard withDefaultEmoji else { return recentReactionEmoji } // Add in our default emoji if desired - let defaultEmoji = ["😂", "🥰", "😢", "😡", "😮", "😈"] + let defaultEmoji = ["😂", "🥰", "😢", "😡", "😮", "😈"] // stringlint:disable .filter { !recentReactionEmoji.contains($0) } return Array(recentReactionEmoji diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 57ce17769..ff5ad3f98 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -56,32 +56,26 @@ class GiphyRendition: ProxiedContentAssetDescription { private class func fileExtension(forFormat format: GiphyFormat) -> String { switch format { - case .gif: - return "gif" - case .mp4: - return "mp4" - case .jpg: - return "jpg" + case .gif: return "gif" // stringlint:disable + case .mp4: return "mp4" // stringlint:disable + case .jpg: return "jpg" // stringlint:disable } } public var utiType: String { switch format { - case .gif: - return kUTTypeGIF as String - case .mp4: - return kUTTypeMPEG4 as String - case .jpg: - return kUTTypeJPEG as String + case .gif: return kUTTypeGIF as String + case .mp4: return kUTTypeMPEG4 as String + case .jpg: return kUTTypeJPEG as String } } public var isStill: Bool { - return name.hasSuffix("_still") + return name.hasSuffix("_still") // stringlint:disable } public var isDownsampled: Bool { - return name.hasSuffix("_downsampled") + return name.hasSuffix("_downsampled") // stringlint:disable } public func log() { @@ -279,11 +273,11 @@ enum GiphyAPI { // MARK: - Search // This is the Signal iOS API key. - private static let kGiphyApiKey = "ZsUpUm2L6cVbvei347EQNp7HrROjbOdc" + private static let kGiphyApiKey = "ZsUpUm2L6cVbvei347EQNp7HrROjbOdc" // stringlint:disable private static let kGiphyPageSize = 20 public static func trending() -> AnyPublisher<[GiphyImageInfo], Error> { - let urlString = "/v1/gifs/trending?api_key=\(kGiphyApiKey)&limit=\(kGiphyPageSize)" + let urlString = "/v1/gifs/trending?api_key=\(kGiphyApiKey)&limit=\(kGiphyPageSize)" // stringlint:disable guard let url: URL = URL(string: "\(kGiphyBaseURL)\(urlString)") else { return Fail(error: HTTPError.invalidURL) @@ -319,10 +313,10 @@ enum GiphyAPI { let url: URL = URL( string: [ kGiphyBaseURL, - "/v1/gifs/search?api_key=\(kGiphyApiKey)", - "&offset=\(kGiphyPageOffset)", - "&limit=\(kGiphyPageSize)", - "&q=\(queryEncoded)" + "/v1/gifs/search?api_key=\(kGiphyApiKey)", // stringlint:disable + "&offset=\(kGiphyPageOffset)", // stringlint:disable + "&limit=\(kGiphyPageSize)", // stringlint:disable + "&q=\(queryEncoded)" // stringlint:disable ].joined() ) else { @@ -370,7 +364,7 @@ enum GiphyAPI { Logger.error("Invalid response.") return nil } - guard let imageDicts = responseDict["data"] as? [[String: Any]] else { + guard let imageDicts = responseDict["data"] as? [[String: Any]] else { // stringlint:disable Logger.error("Invalid response data.") return nil } @@ -381,7 +375,7 @@ enum GiphyAPI { // Giphy API results are often incomplete or malformed, so we need to be defensive. private static func parseGiphyImage(imageDict: [String: Any]) -> GiphyImageInfo? { - guard let giphyId = imageDict["id"] as? String else { + guard let giphyId = imageDict["id"] as? String else { // stringlint:disable Logger.warn("Image dict missing id.") return nil } @@ -389,7 +383,7 @@ enum GiphyAPI { Logger.warn("Image dict has invalid id.") return nil } - guard let renditionDicts = imageDict["images"] as? [String: Any] else { + guard let renditionDicts = imageDict["images"] as? [String: Any] else { // stringlint:disable Logger.warn("Image dict missing renditions.") return nil } @@ -423,7 +417,7 @@ enum GiphyAPI { } private static func findOriginalRendition(renditions: [GiphyRendition]) -> GiphyRendition? { - for rendition in renditions where rendition.name == "original" { + for rendition in renditions where rendition.name == "original" { // stringlint:disable return rendition } return nil @@ -436,15 +430,15 @@ enum GiphyAPI { renditionName: String, renditionDict: [String: Any] ) -> GiphyRendition? { - guard let width = parsePositiveUInt(dict: renditionDict, key: "width", typeName: "rendition") else { + guard let width = parsePositiveUInt(dict: renditionDict, key: "width", typeName: "rendition") else { // stringlint:disable return nil } - guard let height = parsePositiveUInt(dict: renditionDict, key: "height", typeName: "rendition") else { + guard let height = parsePositiveUInt(dict: renditionDict, key: "height", typeName: "rendition") else { // stringlint:disable return nil } // Be lenient when parsing file sizes - we don't require them for stills. - let fileSize = parseLenientUInt(dict: renditionDict, key: "size") - guard let urlString = renditionDict["url"] as? String else { + let fileSize = parseLenientUInt(dict: renditionDict, key: "size") // stringlint:disable + guard let urlString = renditionDict["url"] as? String else { // stringlint:disable return nil } guard urlString.count > 0 else { @@ -460,13 +454,13 @@ enum GiphyAPI { return nil } var format = GiphyFormat.gif - if fileExtension == "gif" { + if fileExtension == "gif" { // stringlint:disable format = .gif - } else if fileExtension == "jpg" { + } else if fileExtension == "jpg" { // stringlint:disable format = .jpg - } else if fileExtension == "mp4" { + } else if fileExtension == "mp4" { // stringlint:disable format = .mp4 - } else if fileExtension == "webp" { + } else if fileExtension == "webp" { // stringlint:disable return nil } else { Logger.warn("Invalid file extension: \(fileExtension).") diff --git a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift index 0ff182537..0e5e78965 100644 --- a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift +++ b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift @@ -145,7 +145,7 @@ public class MediaGalleryViewModel { public struct GalleryDate: Differentiable, Equatable, Comparable, Hashable { private static let thisYearFormatter: DateFormatter = { let formatter = DateFormatter() - formatter.dateFormat = "MMMM" + formatter.dateFormat = "MMMM" // stringlint:disable return formatter }() @@ -153,7 +153,7 @@ public class MediaGalleryViewModel { private static let olderFormatter: DateFormatter = { // FIXME: localize for RTL, or is there a built in way to do this? let formatter = DateFormatter() - formatter.dateFormat = "MMMM yyyy" + formatter.dateFormat = "MMMM yyyy" // stringlint:disable return formatter }() diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index a961aa339..369da45a5 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Combine diff --git a/Session/Meta/Translations/bg.lproj/Localizable.strings b/Session/Meta/Translations/bg.lproj/Localizable.strings index 6672437a5..7d9d1386d 100644 --- a/Session/Meta/Translations/bg.lproj/Localizable.strings +++ b/Session/Meta/Translations/bg.lproj/Localizable.strings @@ -93,8 +93,7 @@ /* Label indicating loading is in progress */ "GALLERY_TILES_LOADING_OLDER_LABEL" = "Зареждане на по-стари файлове…"; /* Error displayed when there is a failure fetching a GIF from the remote service. */ -"GIF_PICKER_ERROR_FETCH_FAILURE" = "Извличането на посочения GIF е неуспешно. -Моля, проверете интернет връзката си."; +"GIF_PICKER_ERROR_FETCH_FAILURE" = "Извличането на посочения GIF е неуспешно. Моля, проверете интернет връзката си."; /* Generic error displayed when picking a GIF */ "GIF_PICKER_ERROR_GENERIC" = "Възникна неизвестна грешка."; /* Shown when selected GIF couldn't be fetched */ diff --git a/Session/Meta/Translations/es-ES.lproj/Localizable.strings b/Session/Meta/Translations/es-ES.lproj/Localizable.strings index 33ad18078..4b75adae8 100644 --- a/Session/Meta/Translations/es-ES.lproj/Localizable.strings +++ b/Session/Meta/Translations/es-ES.lproj/Localizable.strings @@ -486,8 +486,7 @@ "PRIVACY_CALLS_TITLE" = "Llamadas de voz y video"; "PRIVACY_CALLS_DESCRIPTION" = "Habilita llamadas de voz y video hacia y desde otros usuarios."; "PRIVACY_CALLS_WARNING_TITLE" = "Llamadas de Voz y Video (Beta)"; -"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Su dirección IP es visible para su socio de llamadas y un servidor de Oxen Fundación - mientras usas llamadas beta. ¿Está seguro de que desea habilitar las llamadas de voz y videollamadas?"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Su dirección IP es visible para su socio de llamadas y un servidor de Oxen Fundación mientras usas llamadas beta. ¿Está seguro de que desea habilitar las llamadas de voz y videollamadas?"; "NOTIFICATIONS_TITLE" = "Notificaciones"; "NOTIFICATIONS_SECTION_STRATEGY" = "Estrategia de notificación"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Usar Modo Rápido"; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 2c0774346..3b6c88171 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -24,68 +24,6 @@ import SessionSnodeKit /// there is no need for an Adapter, and instead the appropriate NotificationActionHandler is /// wired directly into the appropriate callback point. -enum AppNotificationCategory: CaseIterable { - case incomingMessage - case incomingMessageFromNoLongerVerifiedIdentity - case errorMessage - case threadlessErrorMessage -} - -enum AppNotificationAction: CaseIterable { - case markAsRead - case reply - case showThread -} - -struct AppNotificationUserInfoKey { - static let threadId = "Signal.AppNotificationsUserInfoKey.threadId" - static let threadVariantRaw = "Signal.AppNotificationsUserInfoKey.threadVariantRaw" - static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber" - static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId" - static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter" -} - -extension AppNotificationCategory { - var identifier: String { - switch self { - case .incomingMessage: - return "Signal.AppNotificationCategory.incomingMessage" - case .incomingMessageFromNoLongerVerifiedIdentity: - return "Signal.AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity" - case .errorMessage: - return "Signal.AppNotificationCategory.errorMessage" - case .threadlessErrorMessage: - return "Signal.AppNotificationCategory.threadlessErrorMessage" - } - } - - var actions: [AppNotificationAction] { - switch self { - case .incomingMessage: - return [.markAsRead, .reply] - case .incomingMessageFromNoLongerVerifiedIdentity: - return [.markAsRead, .showThread] - case .errorMessage: - return [.showThread] - case .threadlessErrorMessage: - return [] - } - } -} - -extension AppNotificationAction { - var identifier: String { - switch self { - case .markAsRead: - return "Signal.AppNotifications.Action.markAsRead" - case .reply: - return "Signal.AppNotifications.Action.reply" - case .showThread: - return "Signal.AppNotifications.Action.showThread" - } - } -} - let kAudioNotificationsThrottleCount = 2 let kAudioNotificationsThrottleInterval: TimeInterval = 5 diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 7683ebbb7..5062637ad 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -183,6 +183,6 @@ private func redact(_ string: String) -> String { #if DEBUG return string #else - return "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" + return "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" // stringlint:disable #endif } diff --git a/Session/Notifications/Types/AppNotificationAction.swift b/Session/Notifications/Types/AppNotificationAction.swift new file mode 100644 index 000000000..90c4e3bce --- /dev/null +++ b/Session/Notifications/Types/AppNotificationAction.swift @@ -0,0 +1,21 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation + +enum AppNotificationAction: CaseIterable { + case markAsRead + case reply + case showThread +} + +extension AppNotificationAction { + var identifier: String { + switch self { + case .markAsRead: return "Signal.AppNotifications.Action.markAsRead" + case .reply: return "Signal.AppNotifications.Action.reply" + case .showThread: return "Signal.AppNotifications.Action.showThread" + } + } +} diff --git a/Session/Notifications/Types/AppNotificationCategory.swift b/Session/Notifications/Types/AppNotificationCategory.swift new file mode 100644 index 000000000..5325271c0 --- /dev/null +++ b/Session/Notifications/Types/AppNotificationCategory.swift @@ -0,0 +1,34 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation + +enum AppNotificationCategory: CaseIterable { + case incomingMessage + case incomingMessageFromNoLongerVerifiedIdentity + case errorMessage + case threadlessErrorMessage +} + +extension AppNotificationCategory { + var identifier: String { + switch self { + case .incomingMessage: return "Signal.AppNotificationCategory.incomingMessage" + case .incomingMessageFromNoLongerVerifiedIdentity: + return "Signal.AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity" + + case .errorMessage: return "Signal.AppNotificationCategory.errorMessage" + case .threadlessErrorMessage: return "Signal.AppNotificationCategory.threadlessErrorMessage" + } + } + + var actions: [AppNotificationAction] { + switch self { + case .incomingMessage: return [.markAsRead, .reply] + case .incomingMessageFromNoLongerVerifiedIdentity: return [.markAsRead, .showThread] + case .errorMessage: return [.showThread] + case .threadlessErrorMessage: return [] + } + } +} diff --git a/Session/Notifications/Types/AppNotificationUserInfoKey.swift b/Session/Notifications/Types/AppNotificationUserInfoKey.swift new file mode 100644 index 000000000..101b062e4 --- /dev/null +++ b/Session/Notifications/Types/AppNotificationUserInfoKey.swift @@ -0,0 +1,13 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation + +struct AppNotificationUserInfoKey { + static let threadId = "Signal.AppNotificationsUserInfoKey.threadId" + static let threadVariantRaw = "Signal.AppNotificationsUserInfoKey.threadVariantRaw" + static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber" + static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId" + static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter" +} diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index ef9cb8228..6dfe273a6 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -191,7 +191,7 @@ final class RegisterVC : BaseVC { for index in indexesToShuffle { let startIndex = mangledHexEncodedPublicKey.index(mangledHexEncodedPublicKey.startIndex, offsetBy: index) let endIndex = mangledHexEncodedPublicKey.index(after: startIndex) - mangledHexEncodedPublicKey.replaceSubrange(startIndex.. String { if MIMETypeUtil.isImage(contentType) { - return "📷" + return "📷" // stringlint:disable } else if MIMETypeUtil.isVideo(contentType) { - return "🎥" + return "🎥" // stringlint:disable } else if MIMETypeUtil.isAudio(contentType) { - return "🎧" + return "🎧" // stringlint:disable } else if MIMETypeUtil.isAnimated(contentType) { - return "🎡" + return "🎡" // stringlint:disable } - return "📎" + return "📎" // stringlint:disable } public var description: String { diff --git a/SessionMessagingKit/Database/Models/OpenGroup.swift b/SessionMessagingKit/Database/Models/OpenGroup.swift index d4b27a35c..24cc4a2c4 100644 --- a/SessionMessagingKit/Database/Models/OpenGroup.swift +++ b/SessionMessagingKit/Database/Models/OpenGroup.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB diff --git a/SessionMessagingKit/Open Groups/Crypto/OpenGroupAPI+Crypto.swift b/SessionMessagingKit/Open Groups/Crypto/OpenGroupAPI+Crypto.swift index b4cdb41c5..eeffbc0c8 100644 --- a/SessionMessagingKit/Open Groups/Crypto/OpenGroupAPI+Crypto.swift +++ b/SessionMessagingKit/Open Groups/Crypto/OpenGroupAPI+Crypto.swift @@ -1,4 +1,6 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import CryptoKit diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 5c5d622c0..28e3d22f2 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Combine diff --git a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift b/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift index 9b844a9bf..f37b46180 100644 --- a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift +++ b/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import SessionUtilitiesKit diff --git a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift b/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift index 483d2f253..333786a79 100644 --- a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift +++ b/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import SessionUtilitiesKit diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index 8cc59e6e4..ca57eeead 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift index b2408a869..5eab36e13 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionMessagingKit/Sending & Receiving/Link Previews/HTMLMetadata.swift b/SessionMessagingKit/Sending & Receiving/Link Previews/HTMLMetadata.swift index 5037faaa9..c14977a91 100644 --- a/SessionMessagingKit/Sending & Receiving/Link Previews/HTMLMetadata.swift +++ b/SessionMessagingKit/Sending & Receiving/Link Previews/HTMLMetadata.swift @@ -1,3 +1,5 @@ +// stringlint:disable + import Foundation public struct HTMLMetadata: Equatable { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index 10dc0dde4..feb884e43 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Combine diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 3eaada1b1..76382148c 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 841f844e9..4b6eff040 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB diff --git a/SessionMessagingKit/Utilities/Crypto+SessionMessagingKit.swift b/SessionMessagingKit/Utilities/Crypto+SessionMessagingKit.swift index fa285dd36..1934965d1 100644 --- a/SessionMessagingKit/Utilities/Crypto+SessionMessagingKit.swift +++ b/SessionMessagingKit/Utilities/Crypto+SessionMessagingKit.swift @@ -1,4 +1,6 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Sodium diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift index b8d0ac071..10327ef55 100644 --- a/SessionMessagingKit/Utilities/MessageWrapper.swift +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -1,3 +1,5 @@ +// stringlint:disable + import SessionSnodeKit import SessionUtilitiesKit diff --git a/SessionSnodeKit/Database/LegacyDatabase/SSKLegacy.swift b/SessionSnodeKit/Database/LegacyDatabase/SSKLegacy.swift index 375634444..b96792b59 100644 --- a/SessionSnodeKit/Database/LegacyDatabase/SSKLegacy.swift +++ b/SessionSnodeKit/Database/LegacyDatabase/SSKLegacy.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift index dc023922c..90c0223c4 100644 --- a/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit enum _001_InitialSetupMigration: Migration { static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "initialSetup" + static let identifier: String = "initialSetup" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 diff --git a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift index ca6efbab2..f0efc8bc6 100644 --- a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit /// before running the `YDBToGRDBMigration` enum _002_SetupStandardJobs: Migration { static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "SetupStandardJobs" + static let identifier: String = "SetupStandardJobs" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 diff --git a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift index de155ba1f..35a696bb9 100644 --- a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB diff --git a/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift b/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift index a4bd7babd..af2d69ed8 100644 --- a/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift +++ b/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit enum _004_FlagMessageHashAsDeletedOrInvalid: Migration { static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "FlagMessageHashAsDeletedOrInvalid" + static let identifier: String = "FlagMessageHashAsDeletedOrInvalid" // stringlint:disable static let needsConfigSync: Bool = false /// This migration adds a flat to the `SnodeReceivedMessageInfo` so that when deleting interactions we can diff --git a/SessionSnodeKit/Networking/Notification+OnionRequestAPI.swift b/SessionSnodeKit/Networking/Notification+OnionRequestAPI.swift index 93dc7b042..7b04ba736 100644 --- a/SessionSnodeKit/Networking/Notification+OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/Notification+OnionRequestAPI.swift @@ -1,3 +1,5 @@ +// stringlint:disable + import Foundation public extension Notification.Name { diff --git a/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift index 36aa1c995..c80054165 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Combine diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index 4d0e13b3d..e4e6d505b 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Combine diff --git a/SessionSnodeKit/Types/OnionRequestAPIError.swift b/SessionSnodeKit/Types/OnionRequestAPIError.swift index 43e70da73..2d18bc7d2 100644 --- a/SessionSnodeKit/Types/OnionRequestAPIError.swift +++ b/SessionSnodeKit/Types/OnionRequestAPIError.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import SessionUtilitiesKit diff --git a/SessionSnodeKit/Types/SnodeAPIError.swift b/SessionSnodeKit/Types/SnodeAPIError.swift index a7ec7050f..ef132736b 100644 --- a/SessionSnodeKit/Types/SnodeAPIError.swift +++ b/SessionSnodeKit/Types/SnodeAPIError.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift b/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift index 2951dca42..a43bebaa0 100644 --- a/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift +++ b/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit /// theme preferences enum _001_ThemePreferences: Migration { static let target: TargetMigrations.Identifier = .uiKit - static let identifier: String = "ThemePreferences" + static let identifier: String = "ThemePreferences" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 @@ -18,11 +18,11 @@ enum _001_ThemePreferences: Migration { let isExistingUser: Bool = Identity.userExists(db) let hadCustomLegacyThemeSetting: Bool = UserDefaults.standard.dictionaryRepresentation() .keys - .contains("appMode") + .contains("appMode") // stringlint:disable let matchSystemNightModeSetting: Bool = (isExistingUser && !hadCustomLegacyThemeSetting) let targetTheme: Theme = (!hadCustomLegacyThemeSetting ? Theme.classicDark : - (UserDefaults.standard.integer(forKey: "appMode") == 0 ? + (UserDefaults.standard.integer(forKey: "appMode") == 0 ? // stringlint:disable Theme.classicLight : Theme.classicDark ) diff --git a/SessionUtilitiesKit/Crypto/Hex.swift b/SessionUtilitiesKit/Crypto/Hex.swift index ade2e838b..37c1007be 100644 --- a/SessionUtilitiesKit/Crypto/Hex.swift +++ b/SessionUtilitiesKit/Crypto/Hex.swift @@ -4,7 +4,7 @@ import Foundation public enum Hex { public static func isValid(_ string: String) -> Bool { - let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") + let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") // stringlint:disable return string.uppercased().unicodeScalars.allSatisfy { allowedCharacters.contains($0) } } @@ -32,7 +32,7 @@ public extension Array where Element == UInt8 { self.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) var buffer: UInt8? - var skip = (hex.hasPrefix("0x") ? 2 : 0) + var skip = (hex.hasPrefix("0x") ? 2 : 0) // stringlint:disable for char in hex.unicodeScalars.lazy { guard skip == 0 else { @@ -73,7 +73,7 @@ public extension Array where Element == UInt8 { } func toHexString() -> String { - return map { String(format: "%02x", $0) }.joined() + return map { String(format: "%02x", $0) }.joined() // stringlint:disable } func toBase64(options: Data.Base64EncodingOptions = []) -> String { diff --git a/SessionUtilitiesKit/Crypto/Mnemonic.swift b/SessionUtilitiesKit/Crypto/Mnemonic.swift index 5cabefc7e..c92cb8a50 100644 --- a/SessionUtilitiesKit/Crypto/Mnemonic.swift +++ b/SessionUtilitiesKit/Crypto/Mnemonic.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacy.swift b/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacy.swift index 5d3756d74..97244af8f 100644 --- a/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacy.swift +++ b/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacy.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import YapDatabase diff --git a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift index 797e7c7a4..606e62183 100644 --- a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -5,7 +5,7 @@ import GRDB enum _001_InitialSetupMigration: Migration { static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "initialSetup" + static let identifier: String = "initialSetup" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 diff --git a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift index 7d085d68c..783481634 100644 --- a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -7,7 +7,7 @@ import GRDB /// before running the `YDBToGRDBMigration` enum _002_SetupStandardJobs: Migration { static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "SetupStandardJobs" + static let identifier: String = "SetupStandardJobs" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 diff --git a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift index af10c2651..fb4d3c93e 100644 --- a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -6,7 +6,7 @@ import YapDatabase enum _003_YDBToGRDBMigration: Migration { static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "YDBToGRDBMigration" + static let identifier: String = "YDBToGRDBMigration" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 @@ -28,7 +28,7 @@ enum _003_YDBToGRDBMigration: Migration { // Map the Legacy types for the NSKeyedUnarchiver NSKeyedUnarchiver.setClass( SUKLegacy.KeyPair.self, - forClassName: "ECKeyPair" + forClassName: "ECKeyPair" // stringlint:disable ) dbConnection.read { transaction in diff --git a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift b/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift index 384c25195..6ae534ddc 100644 --- a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift +++ b/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift @@ -6,7 +6,7 @@ import YapDatabase enum _004_AddJobPriority: Migration { static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "AddJobPriority" + static let identifier: String = "AddJobPriority" // stringlint:disable static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 diff --git a/SessionUtilitiesKit/Database/OWSFileSystem.m b/SessionUtilitiesKit/Database/OWSFileSystem.m index bd3eae2a1..fdeb6a060 100644 --- a/SessionUtilitiesKit/Database/OWSFileSystem.m +++ b/SessionUtilitiesKit/Database/OWSFileSystem.m @@ -1,3 +1,5 @@ +// stringlint:disable + #import "OWSFileSystem.h" #import "AppContext.h" diff --git a/SessionUtilitiesKit/Database/SSKKeychainStorage.swift b/SessionUtilitiesKit/Database/SSKKeychainStorage.swift index 175725798..0ee3cd907 100644 --- a/SessionUtilitiesKit/Database/SSKKeychainStorage.swift +++ b/SessionUtilitiesKit/Database/SSKKeychainStorage.swift @@ -1,6 +1,7 @@ // // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +// stringlint:disable import Foundation import SAMKeychain diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index f42118e30..875cb85f5 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import CryptoKit diff --git a/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift index 01d0c64bb..3cf50c933 100644 --- a/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift +++ b/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift @@ -1,4 +1,6 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB diff --git a/SessionUtilitiesKit/General/AppContext.m b/SessionUtilitiesKit/General/AppContext.m index d2d00a177..298adb337 100755 --- a/SessionUtilitiesKit/General/AppContext.m +++ b/SessionUtilitiesKit/General/AppContext.m @@ -1,3 +1,5 @@ +// stringlint:disable + #import "AppContext.h" NS_ASSUME_NONNULL_BEGIN diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index e759e83fd..cf5208648 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import SignalCoreKit diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index a280078c2..a7648765a 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -62,7 +62,7 @@ public enum SNUserDefaults { } public extension UserDefaults { - public static let applicationGroup: String = "group.com.loki-project.loki-messenger" + static let applicationGroup: String = "group.com.loki-project.loki-messenger" @objc static var sharedLokiProject: UserDefaults? { UserDefaults(suiteName: UserDefaults.applicationGroup) diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 7c46c2730..5e80a9ee4 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB diff --git a/SessionUtilitiesKit/Media/MIMETypeUtil.m b/SessionUtilitiesKit/Media/MIMETypeUtil.m index 469898125..ae9124af9 100644 --- a/SessionUtilitiesKit/Media/MIMETypeUtil.m +++ b/SessionUtilitiesKit/Media/MIMETypeUtil.m @@ -1,3 +1,5 @@ +// stringlint:disable + #import "MIMETypeUtil.h" #import "OWSFileSystem.h" diff --git a/SessionUtilitiesKit/Networking/ContentProxy.swift b/SessionUtilitiesKit/Networking/ContentProxy.swift index 5c5710fb8..a9c7298b2 100644 --- a/SessionUtilitiesKit/Networking/ContentProxy.swift +++ b/SessionUtilitiesKit/Networking/ContentProxy.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index d209456c1..a2b6d1bef 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import Combine diff --git a/SessionUtilitiesKit/Networking/HTTPHeader.swift b/SessionUtilitiesKit/Networking/HTTPHeader.swift index 1ace5ed88..4c07c90c1 100644 --- a/SessionUtilitiesKit/Networking/HTTPHeader.swift +++ b/SessionUtilitiesKit/Networking/HTTPHeader.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionUtilitiesKit/Networking/HTTPMethod.swift b/SessionUtilitiesKit/Networking/HTTPMethod.swift index 940ca4fe3..dd9b532c6 100644 --- a/SessionUtilitiesKit/Networking/HTTPMethod.swift +++ b/SessionUtilitiesKit/Networking/HTTPMethod.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionUtilitiesKit/Utilities/Bencode.swift b/SessionUtilitiesKit/Utilities/Bencode.swift index cf9375b50..c478a5c2d 100644 --- a/SessionUtilitiesKit/Utilities/Bencode.swift +++ b/SessionUtilitiesKit/Utilities/Bencode.swift @@ -1,4 +1,6 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SessionUtilitiesKit/Utilities/NSAttributedString+Utilities.swift b/SessionUtilitiesKit/Utilities/NSAttributedString+Utilities.swift index 9afddf115..d030ac240 100644 --- a/SessionUtilitiesKit/Utilities/NSAttributedString+Utilities.swift +++ b/SessionUtilitiesKit/Utilities/NSAttributedString+Utilities.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation diff --git a/SignalUtilitiesKit/Utilities/AppVersion.m b/SignalUtilitiesKit/Utilities/AppVersion.m index 993e99475..a2e48f544 100755 --- a/SignalUtilitiesKit/Utilities/AppVersion.m +++ b/SignalUtilitiesKit/Utilities/AppVersion.m @@ -1,6 +1,7 @@ // // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // +// stringlint:disable #import "AppVersion.h" #import "NSUserDefaults+OWS.h"