diff --git a/AbandonedStrings.xcodeproj/project.pbxproj b/AbandonedStrings.xcodeproj/project.pbxproj index f37ef55..955e065 100644 --- a/AbandonedStrings.xcodeproj/project.pbxproj +++ b/AbandonedStrings.xcodeproj/project.pbxproj @@ -89,7 +89,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1210; ORGANIZATIONNAME = iJoshSmith; TargetAttributes = { 0361F13B1C605FE0009D519A = { @@ -100,10 +100,11 @@ }; buildConfigurationList = 0361F1371C605FE0009D519A /* Build configuration list for PBXProject "AbandonedStrings" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 0361F1331C605FE0009D519A; productRefGroup = 0361F13D1C605FE0009D519A /* Products */; @@ -131,17 +132,29 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -175,17 +188,29 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -204,22 +229,25 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 0361F1441C605FE0009D519A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "-"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; 0361F1451C605FE0009D519A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "-"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/AbandonedStrings.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/AbandonedStrings.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/AbandonedStrings.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/AbandonedStrings/main.swift b/AbandonedStrings/main.swift index 9fffaa8..8556ea3 100755 --- a/AbandonedStrings/main.swift +++ b/AbandonedStrings/main.swift @@ -42,7 +42,7 @@ func contentsOfFile(_ filePath: String) -> String { return try String(contentsOfFile: filePath) } catch { - print("cannot read file!!!") + print(error.localizedDescription) exit(1) } } @@ -52,7 +52,10 @@ func concatenateAllSourceCodeIn(_ directories: [String], withStoryboard: Bool) - if withStoryboard { extensions.append("storyboard") } - let sourceFiles = findFilesIn(directories, withExtensions: extensions) + + var sourceFiles = findFilesIn(directories, withExtensions: extensions) + sourceFiles.removeAll { $0.contains("R.generated.swift") } + return sourceFiles.reduce("") { (accumulator, sourceFile) -> String in return accumulator + contentsOfFile(sourceFile) } @@ -73,20 +76,37 @@ func extractStringIdentifiersFrom(_ stringsFile: String) -> [String] { func extractStringIdentifierFromTrimmedLine(_ line: String) -> String { let indexAfterFirstQuote = line.index(after: line.startIndex) let lineWithoutFirstQuote = line[indexAfterFirstQuote...] - let endIndex = lineWithoutFirstQuote.index(of:"\"")! + let endIndex = lineWithoutFirstQuote.firstIndex(of:"\"")! let identifier = lineWithoutFirstQuote[.. String { + let components = identified.components(separatedBy: ".") + let reducedComponents = components.reduce("") { + if $0.isEmpty { + return $1 + } else { + return $0 + $1.prefix(1).uppercased() + $1.dropFirst() + } + } + + return "R.string.localizable." + reducedComponents +} + func findStringIdentifiersIn(_ stringsFile: String, abandonedBySourceCode sourceCode: String) -> [String] { return extractStringIdentifiersFrom(stringsFile).filter { identifier in let quotedIdentifier = "\"\(identifier)\"" let quotedIdentifierForStoryboard = "\"@\(identifier)\"" let signalQuotedIdentifierForJs = "'\(identifier)'" - let isAbandoned = (sourceCode.contains(quotedIdentifier) == false && sourceCode.contains(quotedIdentifierForStoryboard) == false && - sourceCode.contains(signalQuotedIdentifierForJs) == false) + let rSwiftIdentifier = makeRSwiftIdentifier(from: identifier) + + let isAbandoned = sourceCode.contains(quotedIdentifier) == false && sourceCode.contains(quotedIdentifierForStoryboard) == false && + sourceCode.contains(signalQuotedIdentifierForJs) == false && + sourceCode.contains(rSwiftIdentifier) == false + return isAbandoned } } @@ -138,8 +158,8 @@ func getRootDirectories() -> [String]? { if isOptionalParameterForStoryboardAvailable() { c.removeLast() } - if isOptionaParameterForWritingAvailable() { - c.remove(at: c.index(of: "write")!) + if isOptionalParameterForWritingAvailable() { + c.remove(at: c.firstIndex(of: "write")!) } return c } @@ -148,7 +168,7 @@ func isOptionalParameterForStoryboardAvailable() -> Bool { return CommandLine.arguments.last == "storyboard" } -func isOptionaParameterForWritingAvailable() -> Bool { +func isOptionalParameterForWritingAvailable() -> Bool { return CommandLine.arguments.contains("write") } @@ -173,7 +193,7 @@ if let rootDirectories = getRootDirectories() { print("Abandoned resource strings were detected:") displayAbandonedIdentifiersInMap(map) - if isOptionaParameterForWritingAvailable() { + if isOptionalParameterForWritingAvailable() { map.keys.forEach { (stringsFilePath) in print("\n\nNow modifying \(stringsFilePath) ...") let updatedStringsFileContent = stringsFile(stringsFilePath, without: map[stringsFilePath]!) diff --git a/README.md b/README.md index 26896d1..d350c12 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Abandoned Resource String Detection This command line program detects unused resource strings in an iOS or OS X application. -Updated to Swift 3, thanks to @astaeck on Oct-17-2016 - ## Usage Open a Terminal to the directory which contains the *AbandonedStrings* executable, and run the following command: