├── .gitignore ├── AccessControlKitty.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── zoesmith.xcuserdatad │ └── xcschemes │ ├── AccessControlKitty.xcscheme │ ├── AccessControlKittyExtension.xcscheme │ └── xcschememanagement.plist ├── AccessControlKitty ├── AccessControlKitty.entitlements ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 128@1x.png │ │ ├── 128@1x@2x.png │ │ ├── 16@1x.png │ │ ├── 16@1x@2x.png │ │ ├── 256@1x.png │ │ ├── 256@1x@2x.png │ │ ├── 32@1x.png │ │ ├── 32@1x@2x.png │ │ ├── 512@1x.png │ │ ├── 512@1x@2x.png │ │ └── Contents.json │ ├── Contents.json │ ├── extension.imageset │ │ ├── Contents.json │ │ └── extension.png │ └── menuselection.imageset │ │ ├── Contents.json │ │ └── Screenshot 2019-02-04 20.15.09.png ├── Base.lproj │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── AccessControlKittyExtension ├── AccessControlCommand.swift ├── AccessControlError.swift ├── AccessControlKittyExtension.entitlements └── Info.plist ├── AccessControlKittyTests ├── AccessControlKittyTests.swift └── Info.plist ├── AccessControlWorkspace.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE.txt ├── README.md ├── SwiftLineParser ├── Access.swift ├── Comments.swift ├── Info.plist ├── Lexer.swift ├── LineChange.swift ├── Logging.swift ├── Parser.swift ├── Regex.swift ├── String alterations.swift ├── Structure.swift ├── SwiftLineParser.h ├── Tagged.swift └── Token.swift └── SwiftLineParserTests ├── Info.plist ├── LexerTests.swift └── ParserTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,swift 3 | # Edit at https://www.gitignore.io/?templates=xcode,swift 4 | 5 | ### Swift ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | 37 | 38 | ### Xcode ### 39 | # Xcode 40 | # 41 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 42 | 43 | ## User settings 44 | 45 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 46 | 47 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 48 | 49 | ### Xcode Patch ### 50 | *.xcodeproj/* 51 | !*.xcodeproj/project.pbxproj 52 | !*.xcodeproj/xcshareddata/ 53 | !*.xcworkspace/contents.xcworkspacedata 54 | /*.gcno 55 | **/xcshareddata/WorkspaceSettings.xcsettings 56 | 57 | # End of https://www.gitignore.io/api/xcode,swift 58 | 59 | */UserInterfaceState.xcuserstate 60 | -------------------------------------------------------------------------------- /AccessControlKitty.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | ED0062C22208E6FB00FC48B2 /* SwiftLineParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED18F827209488C1002BD062 /* SwiftLineParser.framework */; }; 11 | ED03E642220610D100957094 /* Structure.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED03E641220610D100957094 /* Structure.swift */; }; 12 | ED03E644220BB24500957094 /* Access.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED03E643220BB24500957094 /* Access.swift */; }; 13 | ED042CE422145A7E0047A861 /* String alterations.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED042CE322145A7E0047A861 /* String alterations.swift */; }; 14 | ED042CE622145AC60047A861 /* LineChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED042CE522145AC60047A861 /* LineChange.swift */; }; 15 | ED18F7FF2094886B002BD062 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F7FE2094886B002BD062 /* AppDelegate.swift */; }; 16 | ED18F8012094886C002BD062 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ED18F8002094886C002BD062 /* Assets.xcassets */; }; 17 | ED18F8132094889C002BD062 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED18F8122094889C002BD062 /* Cocoa.framework */; }; 18 | ED18F81D2094889C002BD062 /* AccessControlKittyExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = ED18F8102094889C002BD062 /* AccessControlKittyExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 19 | ED18F82B209488C1002BD062 /* SwiftLineParser.h in Headers */ = {isa = PBXBuildFile; fileRef = ED18F829209488C1002BD062 /* SwiftLineParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | ED18F82E209488C1002BD062 /* SwiftLineParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED18F827209488C1002BD062 /* SwiftLineParser.framework */; }; 21 | ED18F82F209488C1002BD062 /* SwiftLineParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = ED18F827209488C1002BD062 /* SwiftLineParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 22 | ED18F83D209488D5002BD062 /* SwiftLineParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED18F827209488C1002BD062 /* SwiftLineParser.framework */; }; 23 | ED18F845209488FA002BD062 /* ParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F843209488FA002BD062 /* ParserTests.swift */; }; 24 | ED18F846209488FA002BD062 /* LexerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F844209488FA002BD062 /* LexerTests.swift */; }; 25 | ED18F84F2094890D002BD062 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F8472094890C002BD062 /* Token.swift */; }; 26 | ED18F8502094890D002BD062 /* Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F8482094890C002BD062 /* Comments.swift */; }; 27 | ED18F8522094890D002BD062 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F84A2094890C002BD062 /* Lexer.swift */; }; 28 | ED18F8532094890D002BD062 /* Tagged.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F84B2094890C002BD062 /* Tagged.swift */; }; 29 | ED18F8542094890D002BD062 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F84C2094890C002BD062 /* Parser.swift */; }; 30 | ED18F8552094890D002BD062 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F84D2094890C002BD062 /* Logging.swift */; }; 31 | ED18F8562094890D002BD062 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F84E2094890C002BD062 /* Regex.swift */; }; 32 | ED18F85820948921002BD062 /* AccessControlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F85720948921002BD062 /* AccessControlCommand.swift */; }; 33 | ED9CA45521F945F2004898AD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9CA45421F945F2004898AD /* ViewController.swift */; }; 34 | ED9CA47421FA7B97004898AD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ED9CA47221FA7B97004898AD /* Main.storyboard */; }; 35 | EDA14492225A193200901BBB /* AccessControlError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA14491225A193200901BBB /* AccessControlError.swift */; }; 36 | EDE6D94C22183992002AF4BB /* AccessControlKittyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE6D94B22183992002AF4BB /* AccessControlKittyTests.swift */; }; 37 | EDE6D9582218405F002AF4BB /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDE6D9572218405F002AF4BB /* XcodeKit.framework */; }; 38 | EDE6D959221846C3002AF4BB /* AccessControlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED18F85720948921002BD062 /* AccessControlCommand.swift */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXContainerItemProxy section */ 42 | ED18F81B2094889C002BD062 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = ED18F7F32094886B002BD062 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = ED18F80F2094889C002BD062; 47 | remoteInfo = AccessControlKittyExtension; 48 | }; 49 | ED18F82C209488C1002BD062 /* PBXContainerItemProxy */ = { 50 | isa = PBXContainerItemProxy; 51 | containerPortal = ED18F7F32094886B002BD062 /* Project object */; 52 | proxyType = 1; 53 | remoteGlobalIDString = ED18F826209488C1002BD062; 54 | remoteInfo = SwiftLineParser; 55 | }; 56 | ED18F83E209488D5002BD062 /* PBXContainerItemProxy */ = { 57 | isa = PBXContainerItemProxy; 58 | containerPortal = ED18F7F32094886B002BD062 /* Project object */; 59 | proxyType = 1; 60 | remoteGlobalIDString = ED18F826209488C1002BD062; 61 | remoteInfo = SwiftLineParser; 62 | }; 63 | ED18F85B2094A35A002BD062 /* PBXContainerItemProxy */ = { 64 | isa = PBXContainerItemProxy; 65 | containerPortal = ED18F7F32094886B002BD062 /* Project object */; 66 | proxyType = 1; 67 | remoteGlobalIDString = ED18F826209488C1002BD062; 68 | remoteInfo = SwiftLineParser; 69 | }; 70 | EDE6D94E22183992002AF4BB /* PBXContainerItemProxy */ = { 71 | isa = PBXContainerItemProxy; 72 | containerPortal = ED18F7F32094886B002BD062 /* Project object */; 73 | proxyType = 1; 74 | remoteGlobalIDString = ED18F7FA2094886B002BD062; 75 | remoteInfo = AccessControlKitty; 76 | }; 77 | /* End PBXContainerItemProxy section */ 78 | 79 | /* Begin PBXCopyFilesBuildPhase section */ 80 | ED18F8212094889C002BD062 /* Embed App Extensions */ = { 81 | isa = PBXCopyFilesBuildPhase; 82 | buildActionMask = 2147483647; 83 | dstPath = ""; 84 | dstSubfolderSpec = 13; 85 | files = ( 86 | ED18F81D2094889C002BD062 /* AccessControlKittyExtension.appex in Embed App Extensions */, 87 | ); 88 | name = "Embed App Extensions"; 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | ED18F833209488C1002BD062 /* Embed Frameworks */ = { 92 | isa = PBXCopyFilesBuildPhase; 93 | buildActionMask = 2147483647; 94 | dstPath = ""; 95 | dstSubfolderSpec = 10; 96 | files = ( 97 | ED18F82F209488C1002BD062 /* SwiftLineParser.framework in Embed Frameworks */, 98 | ); 99 | name = "Embed Frameworks"; 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | /* End PBXCopyFilesBuildPhase section */ 103 | 104 | /* Begin PBXFileReference section */ 105 | ED03E641220610D100957094 /* Structure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structure.swift; sourceTree = ""; }; 106 | ED03E643220BB24500957094 /* Access.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Access.swift; sourceTree = ""; }; 107 | ED042CE322145A7E0047A861 /* String alterations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String alterations.swift"; sourceTree = ""; }; 108 | ED042CE522145AC60047A861 /* LineChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChange.swift; sourceTree = ""; }; 109 | ED18F7FB2094886B002BD062 /* AccessControlKitty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AccessControlKitty.app; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | ED18F7FE2094886B002BD062 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 111 | ED18F8002094886C002BD062 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 112 | ED18F8052094886C002BD062 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 113 | ED18F8062094886C002BD062 /* AccessControlKitty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AccessControlKitty.entitlements; sourceTree = ""; }; 114 | ED18F8102094889C002BD062 /* AccessControlKittyExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AccessControlKittyExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 115 | ED18F8122094889C002BD062 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 116 | ED18F8192094889C002BD062 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 117 | ED18F81A2094889C002BD062 /* AccessControlKittyExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AccessControlKittyExtension.entitlements; sourceTree = ""; }; 118 | ED18F827209488C1002BD062 /* SwiftLineParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftLineParser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 119 | ED18F829209488C1002BD062 /* SwiftLineParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftLineParser.h; sourceTree = ""; }; 120 | ED18F82A209488C1002BD062 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 121 | ED18F838209488D5002BD062 /* SwiftLineParserTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLineParserTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 122 | ED18F83C209488D5002BD062 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 123 | ED18F843209488FA002BD062 /* ParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParserTests.swift; sourceTree = ""; }; 124 | ED18F844209488FA002BD062 /* LexerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LexerTests.swift; sourceTree = ""; }; 125 | ED18F8472094890C002BD062 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 126 | ED18F8482094890C002BD062 /* Comments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comments.swift; sourceTree = ""; }; 127 | ED18F84A2094890C002BD062 /* Lexer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lexer.swift; sourceTree = ""; }; 128 | ED18F84B2094890C002BD062 /* Tagged.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tagged.swift; sourceTree = ""; }; 129 | ED18F84C2094890C002BD062 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; 130 | ED18F84D2094890C002BD062 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; 131 | ED18F84E2094890C002BD062 /* Regex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = ""; }; 132 | ED18F85720948921002BD062 /* AccessControlCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessControlCommand.swift; sourceTree = ""; }; 133 | ED9CA45421F945F2004898AD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 134 | ED9CA47321FA7B97004898AD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 135 | EDA14491225A193200901BBB /* AccessControlError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessControlError.swift; sourceTree = ""; }; 136 | EDE6D94922183992002AF4BB /* AccessControlKittyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AccessControlKittyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 137 | EDE6D94B22183992002AF4BB /* AccessControlKittyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessControlKittyTests.swift; sourceTree = ""; }; 138 | EDE6D94D22183992002AF4BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 139 | EDE6D9572218405F002AF4BB /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 140 | /* End PBXFileReference section */ 141 | 142 | /* Begin PBXFrameworksBuildPhase section */ 143 | ED18F7F82094886B002BD062 /* Frameworks */ = { 144 | isa = PBXFrameworksBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | ED18F82E209488C1002BD062 /* SwiftLineParser.framework in Frameworks */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | ED18F80D2094889C002BD062 /* Frameworks */ = { 152 | isa = PBXFrameworksBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | ED0062C22208E6FB00FC48B2 /* SwiftLineParser.framework in Frameworks */, 156 | ED18F8132094889C002BD062 /* Cocoa.framework in Frameworks */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | ED18F823209488C1002BD062 /* Frameworks */ = { 161 | isa = PBXFrameworksBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | ED18F835209488D5002BD062 /* Frameworks */ = { 168 | isa = PBXFrameworksBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | ED18F83D209488D5002BD062 /* SwiftLineParser.framework in Frameworks */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | EDE6D94622183992002AF4BB /* Frameworks */ = { 176 | isa = PBXFrameworksBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | EDE6D9582218405F002AF4BB /* XcodeKit.framework in Frameworks */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXFrameworksBuildPhase section */ 184 | 185 | /* Begin PBXGroup section */ 186 | ED18F7F22094886B002BD062 = { 187 | isa = PBXGroup; 188 | children = ( 189 | ED18F7FD2094886B002BD062 /* AccessControlKitty */, 190 | ED18F8142094889C002BD062 /* AccessControlKittyExtension */, 191 | ED18F828209488C1002BD062 /* SwiftLineParser */, 192 | ED18F839209488D5002BD062 /* SwiftLineParserTests */, 193 | EDE6D94A22183992002AF4BB /* AccessControlKittyTests */, 194 | ED18F8112094889C002BD062 /* Frameworks */, 195 | ED18F7FC2094886B002BD062 /* Products */, 196 | ); 197 | sourceTree = ""; 198 | }; 199 | ED18F7FC2094886B002BD062 /* Products */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | ED18F7FB2094886B002BD062 /* AccessControlKitty.app */, 203 | ED18F8102094889C002BD062 /* AccessControlKittyExtension.appex */, 204 | ED18F827209488C1002BD062 /* SwiftLineParser.framework */, 205 | ED18F838209488D5002BD062 /* SwiftLineParserTests.xctest */, 206 | EDE6D94922183992002AF4BB /* AccessControlKittyTests.xctest */, 207 | ); 208 | name = Products; 209 | sourceTree = ""; 210 | }; 211 | ED18F7FD2094886B002BD062 /* AccessControlKitty */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | ED18F7FE2094886B002BD062 /* AppDelegate.swift */, 215 | ED9CA45421F945F2004898AD /* ViewController.swift */, 216 | ED18F8002094886C002BD062 /* Assets.xcassets */, 217 | ED9CA47221FA7B97004898AD /* Main.storyboard */, 218 | ED18F8052094886C002BD062 /* Info.plist */, 219 | ED18F8062094886C002BD062 /* AccessControlKitty.entitlements */, 220 | ); 221 | path = AccessControlKitty; 222 | sourceTree = ""; 223 | }; 224 | ED18F8112094889C002BD062 /* Frameworks */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | EDE6D9572218405F002AF4BB /* XcodeKit.framework */, 228 | ED18F8122094889C002BD062 /* Cocoa.framework */, 229 | ); 230 | name = Frameworks; 231 | sourceTree = ""; 232 | }; 233 | ED18F8142094889C002BD062 /* AccessControlKittyExtension */ = { 234 | isa = PBXGroup; 235 | children = ( 236 | ED18F85720948921002BD062 /* AccessControlCommand.swift */, 237 | EDA14491225A193200901BBB /* AccessControlError.swift */, 238 | ED18F8192094889C002BD062 /* Info.plist */, 239 | ED18F81A2094889C002BD062 /* AccessControlKittyExtension.entitlements */, 240 | ); 241 | path = AccessControlKittyExtension; 242 | sourceTree = ""; 243 | }; 244 | ED18F828209488C1002BD062 /* SwiftLineParser */ = { 245 | isa = PBXGroup; 246 | children = ( 247 | ED18F829209488C1002BD062 /* SwiftLineParser.h */, 248 | ED18F8482094890C002BD062 /* Comments.swift */, 249 | ED03E643220BB24500957094 /* Access.swift */, 250 | ED042CE322145A7E0047A861 /* String alterations.swift */, 251 | ED042CE522145AC60047A861 /* LineChange.swift */, 252 | ED18F84A2094890C002BD062 /* Lexer.swift */, 253 | ED18F84D2094890C002BD062 /* Logging.swift */, 254 | ED18F84C2094890C002BD062 /* Parser.swift */, 255 | ED03E641220610D100957094 /* Structure.swift */, 256 | ED18F84E2094890C002BD062 /* Regex.swift */, 257 | ED18F84B2094890C002BD062 /* Tagged.swift */, 258 | ED18F8472094890C002BD062 /* Token.swift */, 259 | ED18F82A209488C1002BD062 /* Info.plist */, 260 | ); 261 | path = SwiftLineParser; 262 | sourceTree = ""; 263 | }; 264 | ED18F839209488D5002BD062 /* SwiftLineParserTests */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | ED18F844209488FA002BD062 /* LexerTests.swift */, 268 | ED18F843209488FA002BD062 /* ParserTests.swift */, 269 | ED18F83C209488D5002BD062 /* Info.plist */, 270 | ); 271 | path = SwiftLineParserTests; 272 | sourceTree = ""; 273 | }; 274 | EDE6D94A22183992002AF4BB /* AccessControlKittyTests */ = { 275 | isa = PBXGroup; 276 | children = ( 277 | EDE6D94B22183992002AF4BB /* AccessControlKittyTests.swift */, 278 | EDE6D94D22183992002AF4BB /* Info.plist */, 279 | ); 280 | path = AccessControlKittyTests; 281 | sourceTree = ""; 282 | }; 283 | /* End PBXGroup section */ 284 | 285 | /* Begin PBXHeadersBuildPhase section */ 286 | ED18F824209488C1002BD062 /* Headers */ = { 287 | isa = PBXHeadersBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ED18F82B209488C1002BD062 /* SwiftLineParser.h in Headers */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXHeadersBuildPhase section */ 295 | 296 | /* Begin PBXNativeTarget section */ 297 | ED18F7FA2094886B002BD062 /* AccessControlKitty */ = { 298 | isa = PBXNativeTarget; 299 | buildConfigurationList = ED18F8092094886C002BD062 /* Build configuration list for PBXNativeTarget "AccessControlKitty" */; 300 | buildPhases = ( 301 | ED18F7F72094886B002BD062 /* Sources */, 302 | ED18F7F82094886B002BD062 /* Frameworks */, 303 | ED18F7F92094886B002BD062 /* Resources */, 304 | ED18F8212094889C002BD062 /* Embed App Extensions */, 305 | ED18F833209488C1002BD062 /* Embed Frameworks */, 306 | ); 307 | buildRules = ( 308 | ); 309 | dependencies = ( 310 | ED18F81C2094889C002BD062 /* PBXTargetDependency */, 311 | ED18F82D209488C1002BD062 /* PBXTargetDependency */, 312 | ); 313 | name = AccessControlKitty; 314 | productName = AccessControlKitty; 315 | productReference = ED18F7FB2094886B002BD062 /* AccessControlKitty.app */; 316 | productType = "com.apple.product-type.application"; 317 | }; 318 | ED18F80F2094889C002BD062 /* AccessControlKittyExtension */ = { 319 | isa = PBXNativeTarget; 320 | buildConfigurationList = ED18F81E2094889C002BD062 /* Build configuration list for PBXNativeTarget "AccessControlKittyExtension" */; 321 | buildPhases = ( 322 | ED18F80C2094889C002BD062 /* Sources */, 323 | ED18F80D2094889C002BD062 /* Frameworks */, 324 | ED18F80E2094889C002BD062 /* Resources */, 325 | ); 326 | buildRules = ( 327 | ); 328 | dependencies = ( 329 | ED18F85C2094A35A002BD062 /* PBXTargetDependency */, 330 | ); 331 | name = AccessControlKittyExtension; 332 | productName = AccessControlKittyExtension; 333 | productReference = ED18F8102094889C002BD062 /* AccessControlKittyExtension.appex */; 334 | productType = "com.apple.product-type.xcode-extension"; 335 | }; 336 | ED18F826209488C1002BD062 /* SwiftLineParser */ = { 337 | isa = PBXNativeTarget; 338 | buildConfigurationList = ED18F830209488C1002BD062 /* Build configuration list for PBXNativeTarget "SwiftLineParser" */; 339 | buildPhases = ( 340 | ED18F822209488C1002BD062 /* Sources */, 341 | ED18F823209488C1002BD062 /* Frameworks */, 342 | ED18F824209488C1002BD062 /* Headers */, 343 | ED18F825209488C1002BD062 /* Resources */, 344 | ); 345 | buildRules = ( 346 | ); 347 | dependencies = ( 348 | ); 349 | name = SwiftLineParser; 350 | productName = SwiftLineParser; 351 | productReference = ED18F827209488C1002BD062 /* SwiftLineParser.framework */; 352 | productType = "com.apple.product-type.framework"; 353 | }; 354 | ED18F837209488D5002BD062 /* SwiftLineParserTests */ = { 355 | isa = PBXNativeTarget; 356 | buildConfigurationList = ED18F840209488D5002BD062 /* Build configuration list for PBXNativeTarget "SwiftLineParserTests" */; 357 | buildPhases = ( 358 | ED18F834209488D5002BD062 /* Sources */, 359 | ED18F835209488D5002BD062 /* Frameworks */, 360 | ED18F836209488D5002BD062 /* Resources */, 361 | ); 362 | buildRules = ( 363 | ); 364 | dependencies = ( 365 | ED18F83F209488D5002BD062 /* PBXTargetDependency */, 366 | ); 367 | name = SwiftLineParserTests; 368 | productName = SwiftLineParserTests; 369 | productReference = ED18F838209488D5002BD062 /* SwiftLineParserTests.xctest */; 370 | productType = "com.apple.product-type.bundle.unit-test"; 371 | }; 372 | EDE6D94822183992002AF4BB /* AccessControlKittyTests */ = { 373 | isa = PBXNativeTarget; 374 | buildConfigurationList = EDE6D95222183992002AF4BB /* Build configuration list for PBXNativeTarget "AccessControlKittyTests" */; 375 | buildPhases = ( 376 | EDE6D94522183992002AF4BB /* Sources */, 377 | EDE6D94622183992002AF4BB /* Frameworks */, 378 | EDE6D94722183992002AF4BB /* Resources */, 379 | ); 380 | buildRules = ( 381 | ); 382 | dependencies = ( 383 | EDE6D94F22183992002AF4BB /* PBXTargetDependency */, 384 | ); 385 | name = AccessControlKittyTests; 386 | productName = AccessControlKittyTests; 387 | productReference = EDE6D94922183992002AF4BB /* AccessControlKittyTests.xctest */; 388 | productType = "com.apple.product-type.bundle.unit-test"; 389 | }; 390 | /* End PBXNativeTarget section */ 391 | 392 | /* Begin PBXProject section */ 393 | ED18F7F32094886B002BD062 /* Project object */ = { 394 | isa = PBXProject; 395 | attributes = { 396 | LastSwiftUpdateCheck = 1010; 397 | LastUpgradeCheck = 0930; 398 | ORGANIZATIONNAME = "Hot Beverage"; 399 | TargetAttributes = { 400 | ED18F7FA2094886B002BD062 = { 401 | CreatedOnToolsVersion = 9.3; 402 | LastSwiftMigration = 1010; 403 | }; 404 | ED18F80F2094889C002BD062 = { 405 | CreatedOnToolsVersion = 9.3; 406 | LastSwiftMigration = 1010; 407 | }; 408 | ED18F826209488C1002BD062 = { 409 | CreatedOnToolsVersion = 9.3; 410 | LastSwiftMigration = 1010; 411 | }; 412 | ED18F837209488D5002BD062 = { 413 | CreatedOnToolsVersion = 9.3; 414 | LastSwiftMigration = 1010; 415 | }; 416 | EDE6D94822183992002AF4BB = { 417 | CreatedOnToolsVersion = 10.1; 418 | TestTargetID = ED18F7FA2094886B002BD062; 419 | }; 420 | }; 421 | }; 422 | buildConfigurationList = ED18F7F62094886B002BD062 /* Build configuration list for PBXProject "AccessControlKitty" */; 423 | compatibilityVersion = "Xcode 9.3"; 424 | developmentRegion = en; 425 | hasScannedForEncodings = 0; 426 | knownRegions = ( 427 | en, 428 | Base, 429 | ); 430 | mainGroup = ED18F7F22094886B002BD062; 431 | productRefGroup = ED18F7FC2094886B002BD062 /* Products */; 432 | projectDirPath = ""; 433 | projectRoot = ""; 434 | targets = ( 435 | ED18F7FA2094886B002BD062 /* AccessControlKitty */, 436 | ED18F80F2094889C002BD062 /* AccessControlKittyExtension */, 437 | ED18F826209488C1002BD062 /* SwiftLineParser */, 438 | ED18F837209488D5002BD062 /* SwiftLineParserTests */, 439 | EDE6D94822183992002AF4BB /* AccessControlKittyTests */, 440 | ); 441 | }; 442 | /* End PBXProject section */ 443 | 444 | /* Begin PBXResourcesBuildPhase section */ 445 | ED18F7F92094886B002BD062 /* Resources */ = { 446 | isa = PBXResourcesBuildPhase; 447 | buildActionMask = 2147483647; 448 | files = ( 449 | ED9CA47421FA7B97004898AD /* Main.storyboard in Resources */, 450 | ED18F8012094886C002BD062 /* Assets.xcassets in Resources */, 451 | ); 452 | runOnlyForDeploymentPostprocessing = 0; 453 | }; 454 | ED18F80E2094889C002BD062 /* Resources */ = { 455 | isa = PBXResourcesBuildPhase; 456 | buildActionMask = 2147483647; 457 | files = ( 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | }; 461 | ED18F825209488C1002BD062 /* Resources */ = { 462 | isa = PBXResourcesBuildPhase; 463 | buildActionMask = 2147483647; 464 | files = ( 465 | ); 466 | runOnlyForDeploymentPostprocessing = 0; 467 | }; 468 | ED18F836209488D5002BD062 /* Resources */ = { 469 | isa = PBXResourcesBuildPhase; 470 | buildActionMask = 2147483647; 471 | files = ( 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | }; 475 | EDE6D94722183992002AF4BB /* Resources */ = { 476 | isa = PBXResourcesBuildPhase; 477 | buildActionMask = 2147483647; 478 | files = ( 479 | ); 480 | runOnlyForDeploymentPostprocessing = 0; 481 | }; 482 | /* End PBXResourcesBuildPhase section */ 483 | 484 | /* Begin PBXSourcesBuildPhase section */ 485 | ED18F7F72094886B002BD062 /* Sources */ = { 486 | isa = PBXSourcesBuildPhase; 487 | buildActionMask = 2147483647; 488 | files = ( 489 | ED9CA45521F945F2004898AD /* ViewController.swift in Sources */, 490 | ED18F7FF2094886B002BD062 /* AppDelegate.swift in Sources */, 491 | ); 492 | runOnlyForDeploymentPostprocessing = 0; 493 | }; 494 | ED18F80C2094889C002BD062 /* Sources */ = { 495 | isa = PBXSourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | EDA14492225A193200901BBB /* AccessControlError.swift in Sources */, 499 | ED18F85820948921002BD062 /* AccessControlCommand.swift in Sources */, 500 | ); 501 | runOnlyForDeploymentPostprocessing = 0; 502 | }; 503 | ED18F822209488C1002BD062 /* Sources */ = { 504 | isa = PBXSourcesBuildPhase; 505 | buildActionMask = 2147483647; 506 | files = ( 507 | ED18F8532094890D002BD062 /* Tagged.swift in Sources */, 508 | ED042CE622145AC60047A861 /* LineChange.swift in Sources */, 509 | ED18F8562094890D002BD062 /* Regex.swift in Sources */, 510 | ED042CE422145A7E0047A861 /* String alterations.swift in Sources */, 511 | ED03E642220610D100957094 /* Structure.swift in Sources */, 512 | ED03E644220BB24500957094 /* Access.swift in Sources */, 513 | ED18F84F2094890D002BD062 /* Token.swift in Sources */, 514 | ED18F8542094890D002BD062 /* Parser.swift in Sources */, 515 | ED18F8552094890D002BD062 /* Logging.swift in Sources */, 516 | ED18F8522094890D002BD062 /* Lexer.swift in Sources */, 517 | ED18F8502094890D002BD062 /* Comments.swift in Sources */, 518 | ); 519 | runOnlyForDeploymentPostprocessing = 0; 520 | }; 521 | ED18F834209488D5002BD062 /* Sources */ = { 522 | isa = PBXSourcesBuildPhase; 523 | buildActionMask = 2147483647; 524 | files = ( 525 | ED18F845209488FA002BD062 /* ParserTests.swift in Sources */, 526 | ED18F846209488FA002BD062 /* LexerTests.swift in Sources */, 527 | ); 528 | runOnlyForDeploymentPostprocessing = 0; 529 | }; 530 | EDE6D94522183992002AF4BB /* Sources */ = { 531 | isa = PBXSourcesBuildPhase; 532 | buildActionMask = 2147483647; 533 | files = ( 534 | EDE6D94C22183992002AF4BB /* AccessControlKittyTests.swift in Sources */, 535 | EDE6D959221846C3002AF4BB /* AccessControlCommand.swift in Sources */, 536 | ); 537 | runOnlyForDeploymentPostprocessing = 0; 538 | }; 539 | /* End PBXSourcesBuildPhase section */ 540 | 541 | /* Begin PBXTargetDependency section */ 542 | ED18F81C2094889C002BD062 /* PBXTargetDependency */ = { 543 | isa = PBXTargetDependency; 544 | target = ED18F80F2094889C002BD062 /* AccessControlKittyExtension */; 545 | targetProxy = ED18F81B2094889C002BD062 /* PBXContainerItemProxy */; 546 | }; 547 | ED18F82D209488C1002BD062 /* PBXTargetDependency */ = { 548 | isa = PBXTargetDependency; 549 | target = ED18F826209488C1002BD062 /* SwiftLineParser */; 550 | targetProxy = ED18F82C209488C1002BD062 /* PBXContainerItemProxy */; 551 | }; 552 | ED18F83F209488D5002BD062 /* PBXTargetDependency */ = { 553 | isa = PBXTargetDependency; 554 | target = ED18F826209488C1002BD062 /* SwiftLineParser */; 555 | targetProxy = ED18F83E209488D5002BD062 /* PBXContainerItemProxy */; 556 | }; 557 | ED18F85C2094A35A002BD062 /* PBXTargetDependency */ = { 558 | isa = PBXTargetDependency; 559 | target = ED18F826209488C1002BD062 /* SwiftLineParser */; 560 | targetProxy = ED18F85B2094A35A002BD062 /* PBXContainerItemProxy */; 561 | }; 562 | EDE6D94F22183992002AF4BB /* PBXTargetDependency */ = { 563 | isa = PBXTargetDependency; 564 | target = ED18F7FA2094886B002BD062 /* AccessControlKitty */; 565 | targetProxy = EDE6D94E22183992002AF4BB /* PBXContainerItemProxy */; 566 | }; 567 | /* End PBXTargetDependency section */ 568 | 569 | /* Begin PBXVariantGroup section */ 570 | ED9CA47221FA7B97004898AD /* Main.storyboard */ = { 571 | isa = PBXVariantGroup; 572 | children = ( 573 | ED9CA47321FA7B97004898AD /* Base */, 574 | ); 575 | name = Main.storyboard; 576 | sourceTree = ""; 577 | }; 578 | /* End PBXVariantGroup section */ 579 | 580 | /* Begin XCBuildConfiguration section */ 581 | ED18F8072094886C002BD062 /* Debug */ = { 582 | isa = XCBuildConfiguration; 583 | buildSettings = { 584 | ALWAYS_SEARCH_USER_PATHS = NO; 585 | CLANG_ANALYZER_NONNULL = YES; 586 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 587 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 588 | CLANG_CXX_LIBRARY = "libc++"; 589 | CLANG_ENABLE_MODULES = YES; 590 | CLANG_ENABLE_OBJC_ARC = YES; 591 | CLANG_ENABLE_OBJC_WEAK = YES; 592 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 593 | CLANG_WARN_BOOL_CONVERSION = YES; 594 | CLANG_WARN_COMMA = YES; 595 | CLANG_WARN_CONSTANT_CONVERSION = YES; 596 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 597 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 598 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 599 | CLANG_WARN_EMPTY_BODY = YES; 600 | CLANG_WARN_ENUM_CONVERSION = YES; 601 | CLANG_WARN_INFINITE_RECURSION = YES; 602 | CLANG_WARN_INT_CONVERSION = YES; 603 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 604 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 605 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 606 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 607 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 608 | CLANG_WARN_STRICT_PROTOTYPES = YES; 609 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 610 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 611 | CLANG_WARN_UNREACHABLE_CODE = YES; 612 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 613 | CODE_SIGN_IDENTITY = "Mac Developer"; 614 | COPY_PHASE_STRIP = NO; 615 | CURRENT_PROJECT_VERSION = 9; 616 | DEBUG_INFORMATION_FORMAT = dwarf; 617 | ENABLE_STRICT_OBJC_MSGSEND = YES; 618 | ENABLE_TESTABILITY = YES; 619 | GCC_C_LANGUAGE_STANDARD = gnu11; 620 | GCC_DYNAMIC_NO_PIC = NO; 621 | GCC_NO_COMMON_BLOCKS = YES; 622 | GCC_OPTIMIZATION_LEVEL = 0; 623 | GCC_PREPROCESSOR_DEFINITIONS = ( 624 | "DEBUG=1", 625 | "$(inherited)", 626 | ); 627 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 628 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 629 | GCC_WARN_UNDECLARED_SELECTOR = YES; 630 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 631 | GCC_WARN_UNUSED_FUNCTION = YES; 632 | GCC_WARN_UNUSED_VARIABLE = YES; 633 | MACOSX_DEPLOYMENT_TARGET = 10.14; 634 | MTL_ENABLE_DEBUG_INFO = YES; 635 | ONLY_ACTIVE_ARCH = YES; 636 | SDKROOT = macosx; 637 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 638 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 639 | VERSIONING_SYSTEM = "apple-generic"; 640 | }; 641 | name = Debug; 642 | }; 643 | ED18F8082094886C002BD062 /* Release */ = { 644 | isa = XCBuildConfiguration; 645 | buildSettings = { 646 | ALWAYS_SEARCH_USER_PATHS = NO; 647 | CLANG_ANALYZER_NONNULL = YES; 648 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 649 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 650 | CLANG_CXX_LIBRARY = "libc++"; 651 | CLANG_ENABLE_MODULES = YES; 652 | CLANG_ENABLE_OBJC_ARC = YES; 653 | CLANG_ENABLE_OBJC_WEAK = YES; 654 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 655 | CLANG_WARN_BOOL_CONVERSION = YES; 656 | CLANG_WARN_COMMA = YES; 657 | CLANG_WARN_CONSTANT_CONVERSION = YES; 658 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 659 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 660 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 661 | CLANG_WARN_EMPTY_BODY = YES; 662 | CLANG_WARN_ENUM_CONVERSION = YES; 663 | CLANG_WARN_INFINITE_RECURSION = YES; 664 | CLANG_WARN_INT_CONVERSION = YES; 665 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 666 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 667 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 668 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 669 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 670 | CLANG_WARN_STRICT_PROTOTYPES = YES; 671 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 672 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 673 | CLANG_WARN_UNREACHABLE_CODE = YES; 674 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 675 | CODE_SIGN_IDENTITY = "Mac Developer"; 676 | COPY_PHASE_STRIP = NO; 677 | CURRENT_PROJECT_VERSION = 9; 678 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 679 | ENABLE_NS_ASSERTIONS = NO; 680 | ENABLE_STRICT_OBJC_MSGSEND = YES; 681 | GCC_C_LANGUAGE_STANDARD = gnu11; 682 | GCC_NO_COMMON_BLOCKS = YES; 683 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 684 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 685 | GCC_WARN_UNDECLARED_SELECTOR = YES; 686 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 687 | GCC_WARN_UNUSED_FUNCTION = YES; 688 | GCC_WARN_UNUSED_VARIABLE = YES; 689 | MACOSX_DEPLOYMENT_TARGET = 10.14; 690 | MTL_ENABLE_DEBUG_INFO = NO; 691 | SDKROOT = macosx; 692 | SWIFT_COMPILATION_MODE = wholemodule; 693 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 694 | VERSIONING_SYSTEM = "apple-generic"; 695 | }; 696 | name = Release; 697 | }; 698 | ED18F80A2094886C002BD062 /* Debug */ = { 699 | isa = XCBuildConfiguration; 700 | buildSettings = { 701 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 702 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 703 | CODE_SIGN_ENTITLEMENTS = AccessControlKitty/AccessControlKitty.entitlements; 704 | CODE_SIGN_IDENTITY = "Mac Developer"; 705 | CODE_SIGN_STYLE = Manual; 706 | COMBINE_HIDPI_IMAGES = YES; 707 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 708 | INFOPLIST_FILE = AccessControlKitty/Info.plist; 709 | LD_RUNPATH_SEARCH_PATHS = ( 710 | "$(inherited)", 711 | "@executable_path/../Frameworks", 712 | ); 713 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.accesscontrolkitty; 714 | PRODUCT_NAME = "$(TARGET_NAME)"; 715 | PROVISIONING_PROFILE_SPECIFIER = ""; 716 | SWIFT_VERSION = 4.2; 717 | }; 718 | name = Debug; 719 | }; 720 | ED18F80B2094886C002BD062 /* Release */ = { 721 | isa = XCBuildConfiguration; 722 | buildSettings = { 723 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 724 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 725 | CODE_SIGN_ENTITLEMENTS = AccessControlKitty/AccessControlKitty.entitlements; 726 | CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; 727 | CODE_SIGN_STYLE = Manual; 728 | COMBINE_HIDPI_IMAGES = YES; 729 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 730 | INFOPLIST_FILE = AccessControlKitty/Info.plist; 731 | LD_RUNPATH_SEARCH_PATHS = ( 732 | "$(inherited)", 733 | "@executable_path/../Frameworks", 734 | ); 735 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.accesscontrolkitty; 736 | PRODUCT_NAME = "$(TARGET_NAME)"; 737 | PROVISIONING_PROFILE_SPECIFIER = ""; 738 | SWIFT_VERSION = 4.2; 739 | }; 740 | name = Release; 741 | }; 742 | ED18F81F2094889C002BD062 /* Debug */ = { 743 | isa = XCBuildConfiguration; 744 | buildSettings = { 745 | CODE_SIGN_ENTITLEMENTS = AccessControlKittyExtension/AccessControlKittyExtension.entitlements; 746 | CODE_SIGN_STYLE = Manual; 747 | COMBINE_HIDPI_IMAGES = YES; 748 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 749 | INFOPLIST_FILE = AccessControlKittyExtension/Info.plist; 750 | LD_RUNPATH_SEARCH_PATHS = ( 751 | "$(inherited)", 752 | "@executable_path/../Frameworks", 753 | "@executable_path/../../../../Frameworks", 754 | ); 755 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.accesscontrolkitty.extension; 756 | PRODUCT_NAME = "$(TARGET_NAME)"; 757 | PROVISIONING_PROFILE_SPECIFIER = ""; 758 | SKIP_INSTALL = YES; 759 | SWIFT_VERSION = 4.2; 760 | }; 761 | name = Debug; 762 | }; 763 | ED18F8202094889C002BD062 /* Release */ = { 764 | isa = XCBuildConfiguration; 765 | buildSettings = { 766 | CODE_SIGN_ENTITLEMENTS = AccessControlKittyExtension/AccessControlKittyExtension.entitlements; 767 | CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; 768 | CODE_SIGN_STYLE = Manual; 769 | COMBINE_HIDPI_IMAGES = YES; 770 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 771 | INFOPLIST_FILE = AccessControlKittyExtension/Info.plist; 772 | LD_RUNPATH_SEARCH_PATHS = ( 773 | "$(inherited)", 774 | "@executable_path/../Frameworks", 775 | "@executable_path/../../../../Frameworks", 776 | ); 777 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.accesscontrolkitty.extension; 778 | PRODUCT_NAME = "$(TARGET_NAME)"; 779 | PROVISIONING_PROFILE_SPECIFIER = ""; 780 | SKIP_INSTALL = YES; 781 | SWIFT_VERSION = 4.2; 782 | }; 783 | name = Release; 784 | }; 785 | ED18F831209488C1002BD062 /* Debug */ = { 786 | isa = XCBuildConfiguration; 787 | buildSettings = { 788 | APPLICATION_EXTENSION_API_ONLY = YES; 789 | CLANG_ENABLE_MODULES = YES; 790 | CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; 791 | CODE_SIGN_STYLE = Manual; 792 | COMBINE_HIDPI_IMAGES = YES; 793 | CURRENT_PROJECT_VERSION = 9; 794 | DEFINES_MODULE = YES; 795 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 796 | DYLIB_COMPATIBILITY_VERSION = 1; 797 | DYLIB_CURRENT_VERSION = 9; 798 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 799 | FRAMEWORK_VERSION = A; 800 | INFOPLIST_FILE = SwiftLineParser/Info.plist; 801 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 802 | LD_RUNPATH_SEARCH_PATHS = ( 803 | "$(inherited)", 804 | "@executable_path/../Frameworks", 805 | "@loader_path/Frameworks", 806 | ); 807 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.SwiftLineParser; 808 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 809 | PROVISIONING_PROFILE_SPECIFIER = ""; 810 | SKIP_INSTALL = YES; 811 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 812 | SWIFT_VERSION = 4.2; 813 | VERSIONING_SYSTEM = "apple-generic"; 814 | VERSION_INFO_PREFIX = ""; 815 | }; 816 | name = Debug; 817 | }; 818 | ED18F832209488C1002BD062 /* Release */ = { 819 | isa = XCBuildConfiguration; 820 | buildSettings = { 821 | APPLICATION_EXTENSION_API_ONLY = YES; 822 | CLANG_ENABLE_MODULES = YES; 823 | CODE_SIGN_IDENTITY = ""; 824 | CODE_SIGN_STYLE = Manual; 825 | COMBINE_HIDPI_IMAGES = YES; 826 | CURRENT_PROJECT_VERSION = 9; 827 | DEFINES_MODULE = YES; 828 | DEVELOPMENT_TEAM = ""; 829 | DYLIB_COMPATIBILITY_VERSION = 1; 830 | DYLIB_CURRENT_VERSION = 9; 831 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 832 | FRAMEWORK_VERSION = A; 833 | INFOPLIST_FILE = SwiftLineParser/Info.plist; 834 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 835 | LD_RUNPATH_SEARCH_PATHS = ( 836 | "$(inherited)", 837 | "@executable_path/../Frameworks", 838 | "@loader_path/Frameworks", 839 | ); 840 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.SwiftLineParser; 841 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 842 | PROVISIONING_PROFILE_SPECIFIER = ""; 843 | SKIP_INSTALL = YES; 844 | SWIFT_VERSION = 4.2; 845 | VERSIONING_SYSTEM = "apple-generic"; 846 | VERSION_INFO_PREFIX = ""; 847 | }; 848 | name = Release; 849 | }; 850 | ED18F841209488D5002BD062 /* Debug */ = { 851 | isa = XCBuildConfiguration; 852 | buildSettings = { 853 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 854 | CODE_SIGN_STYLE = Manual; 855 | COMBINE_HIDPI_IMAGES = YES; 856 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 857 | INFOPLIST_FILE = SwiftLineParserTests/Info.plist; 858 | LD_RUNPATH_SEARCH_PATHS = ( 859 | "$(inherited)", 860 | "@executable_path/../Frameworks", 861 | "@loader_path/../Frameworks", 862 | ); 863 | PRODUCT_BUNDLE_IDENTIFIER = hotbeverage.SwiftLineParserTests; 864 | PRODUCT_NAME = "$(TARGET_NAME)"; 865 | PROVISIONING_PROFILE_SPECIFIER = ""; 866 | SWIFT_VERSION = 4.2; 867 | }; 868 | name = Debug; 869 | }; 870 | ED18F842209488D5002BD062 /* Release */ = { 871 | isa = XCBuildConfiguration; 872 | buildSettings = { 873 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 874 | CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; 875 | CODE_SIGN_STYLE = Manual; 876 | COMBINE_HIDPI_IMAGES = YES; 877 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 878 | INFOPLIST_FILE = SwiftLineParserTests/Info.plist; 879 | LD_RUNPATH_SEARCH_PATHS = ( 880 | "$(inherited)", 881 | "@executable_path/../Frameworks", 882 | "@loader_path/../Frameworks", 883 | ); 884 | PRODUCT_BUNDLE_IDENTIFIER = hotbeverage.SwiftLineParserTests; 885 | PRODUCT_NAME = "$(TARGET_NAME)"; 886 | PROVISIONING_PROFILE_SPECIFIER = ""; 887 | SWIFT_VERSION = 4.2; 888 | }; 889 | name = Release; 890 | }; 891 | EDE6D95022183992002AF4BB /* Debug */ = { 892 | isa = XCBuildConfiguration; 893 | buildSettings = { 894 | BUNDLE_LOADER = "$(TEST_HOST)"; 895 | CODE_SIGN_IDENTITY = "Mac Developer"; 896 | CODE_SIGN_STYLE = Manual; 897 | COMBINE_HIDPI_IMAGES = YES; 898 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 899 | FRAMEWORK_SEARCH_PATHS = "${DEVELOPER_FRAMEWORKS_DIR}"; 900 | INFOPLIST_FILE = AccessControlKittyTests/Info.plist; 901 | LD_RUNPATH_SEARCH_PATHS = ( 902 | "$(inherited)", 903 | "@executable_path/../Frameworks", 904 | "@loader_path/../Frameworks", 905 | "${DEVELOPER_FRAMEWORKS_DIR}", 906 | ); 907 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 908 | MTL_FAST_MATH = YES; 909 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.AccessControlKittyTests; 910 | PRODUCT_NAME = "$(TARGET_NAME)"; 911 | PROVISIONING_PROFILE_SPECIFIER = ""; 912 | SWIFT_VERSION = 4.2; 913 | SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; 914 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AccessControlKitty.app/Contents/MacOS/AccessControlKitty"; 915 | }; 916 | name = Debug; 917 | }; 918 | EDE6D95122183992002AF4BB /* Release */ = { 919 | isa = XCBuildConfiguration; 920 | buildSettings = { 921 | BUNDLE_LOADER = "$(TEST_HOST)"; 922 | CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; 923 | CODE_SIGN_STYLE = Manual; 924 | COMBINE_HIDPI_IMAGES = YES; 925 | DEVELOPMENT_TEAM = 9KT5BGB8MA; 926 | FRAMEWORK_SEARCH_PATHS = "${DEVELOPER_FRAMEWORKS_DIR}"; 927 | INFOPLIST_FILE = AccessControlKittyTests/Info.plist; 928 | LD_RUNPATH_SEARCH_PATHS = ( 929 | "$(inherited)", 930 | "@executable_path/../Frameworks", 931 | "@loader_path/../Frameworks", 932 | "${DEVELOPER_FRAMEWORKS_DIR}", 933 | ); 934 | MTL_FAST_MATH = YES; 935 | PRODUCT_BUNDLE_IDENTIFIER = com.hotbeverage.AccessControlKittyTests; 936 | PRODUCT_NAME = "$(TARGET_NAME)"; 937 | PROVISIONING_PROFILE_SPECIFIER = ""; 938 | SWIFT_VERSION = 4.2; 939 | SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; 940 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AccessControlKitty.app/Contents/MacOS/AccessControlKitty"; 941 | }; 942 | name = Release; 943 | }; 944 | /* End XCBuildConfiguration section */ 945 | 946 | /* Begin XCConfigurationList section */ 947 | ED18F7F62094886B002BD062 /* Build configuration list for PBXProject "AccessControlKitty" */ = { 948 | isa = XCConfigurationList; 949 | buildConfigurations = ( 950 | ED18F8072094886C002BD062 /* Debug */, 951 | ED18F8082094886C002BD062 /* Release */, 952 | ); 953 | defaultConfigurationIsVisible = 0; 954 | defaultConfigurationName = Release; 955 | }; 956 | ED18F8092094886C002BD062 /* Build configuration list for PBXNativeTarget "AccessControlKitty" */ = { 957 | isa = XCConfigurationList; 958 | buildConfigurations = ( 959 | ED18F80A2094886C002BD062 /* Debug */, 960 | ED18F80B2094886C002BD062 /* Release */, 961 | ); 962 | defaultConfigurationIsVisible = 0; 963 | defaultConfigurationName = Release; 964 | }; 965 | ED18F81E2094889C002BD062 /* Build configuration list for PBXNativeTarget "AccessControlKittyExtension" */ = { 966 | isa = XCConfigurationList; 967 | buildConfigurations = ( 968 | ED18F81F2094889C002BD062 /* Debug */, 969 | ED18F8202094889C002BD062 /* Release */, 970 | ); 971 | defaultConfigurationIsVisible = 0; 972 | defaultConfigurationName = Release; 973 | }; 974 | ED18F830209488C1002BD062 /* Build configuration list for PBXNativeTarget "SwiftLineParser" */ = { 975 | isa = XCConfigurationList; 976 | buildConfigurations = ( 977 | ED18F831209488C1002BD062 /* Debug */, 978 | ED18F832209488C1002BD062 /* Release */, 979 | ); 980 | defaultConfigurationIsVisible = 0; 981 | defaultConfigurationName = Release; 982 | }; 983 | ED18F840209488D5002BD062 /* Build configuration list for PBXNativeTarget "SwiftLineParserTests" */ = { 984 | isa = XCConfigurationList; 985 | buildConfigurations = ( 986 | ED18F841209488D5002BD062 /* Debug */, 987 | ED18F842209488D5002BD062 /* Release */, 988 | ); 989 | defaultConfigurationIsVisible = 0; 990 | defaultConfigurationName = Release; 991 | }; 992 | EDE6D95222183992002AF4BB /* Build configuration list for PBXNativeTarget "AccessControlKittyTests" */ = { 993 | isa = XCConfigurationList; 994 | buildConfigurations = ( 995 | EDE6D95022183992002AF4BB /* Debug */, 996 | EDE6D95122183992002AF4BB /* Release */, 997 | ); 998 | defaultConfigurationIsVisible = 0; 999 | defaultConfigurationName = Release; 1000 | }; 1001 | /* End XCConfigurationList section */ 1002 | }; 1003 | rootObject = ED18F7F32094886B002BD062 /* Project object */; 1004 | } 1005 | -------------------------------------------------------------------------------- /AccessControlKitty.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AccessControlKitty.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AccessControlKitty.xcodeproj/xcuserdata/zoesmith.xcuserdatad/xcschemes/AccessControlKitty.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /AccessControlKitty.xcodeproj/xcuserdata/zoesmith.xcuserdatad/xcschemes/AccessControlKittyExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 44 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 64 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 79 | 85 | 86 | 87 | 88 | 89 | 95 | 96 | 97 | 98 | 99 | 100 | 110 | 114 | 115 | 116 | 122 | 123 | 124 | 125 | 128 | 129 | 130 | 131 | 132 | 133 | 140 | 142 | 148 | 149 | 150 | 151 | 153 | 154 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /AccessControlKitty.xcodeproj/xcuserdata/zoesmith.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AccessControlKitty.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | AccessControlKittyExtension.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | SwiftLineParser.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | SwiftLineParserTests.xcscheme 23 | 24 | orderHint 25 | 3 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | ED18F7FA2094886B002BD062 31 | 32 | primary 33 | 34 | 35 | ED18F80F2094889C002BD062 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /AccessControlKitty/AccessControlKitty.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AccessControlKitty/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AccessControlKitty 4 | // 5 | // Created by Zoe Smith on 4/28/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | func applicationDidFinishLaunching(_ aNotification: Notification) { 16 | // Insert code here to initialize your application 17 | } 18 | 19 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 20 | return true 21 | } 22 | 23 | func applicationWillTerminate(_ aNotification: Notification) { 24 | // Insert code here to tear down your application 25 | } 26 | 27 | @IBAction func openAGithubIssue(_ sender: Any) { 28 | let url = URL(string: "https://github.com/zoejessica/AccessControlKitty/issues/new")! 29 | NSWorkspace.shared.open(url) 30 | } 31 | 32 | @IBAction func about(_ sender: Any) { 33 | let url = URL(string: "https://hotbeverage.software/accesscontrolkitty")! 34 | NSWorkspace.shared.open(url) 35 | } 36 | 37 | @IBAction func feedback(_ sender: Any) { 38 | let url = URL(string: "https://twitter.com/zoejessica")! 39 | NSWorkspace.shared.open(url) 40 | } 41 | 42 | @IBAction func githubProject(_ sender: Any) { 43 | let url = URL(string: "https://github.com/zoejessica/AccessControlKitty")! 44 | NSWorkspace.shared.open(url) 45 | } 46 | 47 | @IBAction func readme(_ sender: Any) { 48 | let url = URL(string: "https://github.com/zoejessica/AccessControlKitty/blob/master/README.md")! 49 | NSWorkspace.shared.open(url) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/128@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/128@1x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/128@1x@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/128@1x@2x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/16@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/16@1x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/16@1x@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/16@1x@2x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/256@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/256@1x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/256@1x@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/256@1x@2x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/32@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/32@1x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/32@1x@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/32@1x@2x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/512@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/512@1x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/512@1x@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/AppIcon.appiconset/512@1x@2x.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "16@1x.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "16@1x@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "32@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "32@1x@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "128@1x.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "128@1x@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "256@1x.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "256@1x@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "512@1x.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "512@1x@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/extension.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "extension.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/extension.imageset/extension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/extension.imageset/extension.png -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/menuselection.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Screenshot 2019-02-04 20.15.09.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /AccessControlKitty/Assets.xcassets/menuselection.imageset/Screenshot 2019-02-04 20.15.09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoejessica/AccessControlKitty/6693c3a2c8847ac449b32c71ce5ca8dbc76ecc10/AccessControlKitty/Assets.xcassets/menuselection.imageset/Screenshot 2019-02-04 20.15.09.png -------------------------------------------------------------------------------- /AccessControlKitty/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 2. Restart Xcode. Find the new menu item Access Level of Selection at the bottom of the Editor menu. Run a command on selected Swift code. 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /AccessControlKitty/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | LSApplicationCategoryType 8 | public.app-category.developer-tools 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0.3 23 | CFBundleVersion 24 | 9 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2018-9 Zoe Smith 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /AccessControlKitty/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AccessControlKitty 4 | // 5 | // Created by Zoe Smith on 23/1/19. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ViewController: NSViewController { 12 | 13 | @IBOutlet weak var instructions: NSTextField! 14 | 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | // Do view setup here. 19 | } 20 | 21 | @IBAction func openExtensions(_ sender: Any) { 22 | let url = URL(string: "x-apple.systempreferences:com.apple.preferences")! 23 | NSWorkspace.shared.open(url) 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /AccessControlKittyExtension/AccessControlCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessControlCommand.swift 3 | // Change Access Level 4 | // 5 | // Created by Zoe Smith on 4/25/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | import XcodeKit 11 | import SwiftLineParser 12 | 13 | class AccessControlCommand: NSObject, XCSourceEditorCommand { 14 | 15 | func selectedLines(in buffer: XCSourceTextBuffer) -> [Int] { 16 | guard let selections = buffer.selections as? [XCSourceTextRange] else { return [] } 17 | let selectedLines = selections.flatMap { lines($0, totalLinesInBuffer: buffer.lines.count) } 18 | let set = Set.init(selectedLines) 19 | return Array(set).sorted() 20 | } 21 | 22 | func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { 23 | // Implement your command here, invoking the completion handler when done. Pass it nil on success, and an NSError on failure. 24 | guard (invocation.buffer.contentUTI == "com.apple.dt.playground") || (invocation.buffer.contentUTI == "public.swift-source") else { 25 | completionHandler(AccessControlError.unsupportedContentType) 26 | return 27 | } 28 | 29 | guard let accessLevel = AccessChange.init(commandIdentifier: invocation.commandIdentifier) else { 30 | completionHandler(nil) 31 | return 32 | 33 | } 34 | 35 | do { 36 | try changeAccessLevel(accessLevel, invocation.buffer) 37 | completionHandler(nil) 38 | } catch { 39 | completionHandler(error) 40 | } 41 | } 42 | 43 | func changeAccessLevel(_ access: AccessChange, _ buffer: XCSourceTextBuffer) throws { 44 | guard let lines = buffer.lines as? [String] else { return } 45 | 46 | let selectedLineNumbers = selectedLines(in: buffer) 47 | 48 | guard !selectedLineNumbers.isEmpty else { 49 | throw AccessControlError.noSelection 50 | } 51 | 52 | let parser = Parser(lines: lines) 53 | let changedSelections = parser.newLines(at: Array(selectedLineNumbers), accessChange: access) 54 | for lineNumber in selectedLineNumbers { 55 | if let line = changedSelections[lineNumber] { 56 | buffer.lines[lineNumber] = line 57 | } 58 | } 59 | } 60 | } 61 | 62 | func lines(_ range: XCSourceTextRange, totalLinesInBuffer: Int) -> [Int] { 63 | // Always include the whole line UNLESS the start and end positions are exactly the same, in which return an empty array 64 | if range.start.line == range.end.line && range.start.column == range.end.column { 65 | return [] 66 | } else if totalLinesInBuffer == range.end.line { 67 | return Array(range.start.line.. 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AccessControlKittyExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | AccessControlKitty 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Access Level of Selection 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0.3 21 | CFBundleVersion 22 | 9 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | XCSourceEditorCommandDefinitions 30 | 31 | 32 | XCSourceEditorCommandClassName 33 | $(PRODUCT_MODULE_NAME).AccessControlCommand 34 | XCSourceEditorCommandIdentifier 35 | $(PRODUCT_BUNDLE_IDENTIFIER).IncreaseAccess 36 | XCSourceEditorCommandName 37 | Increment Access Levels 38 | 39 | 40 | XCSourceEditorCommandClassName 41 | $(PRODUCT_MODULE_NAME).AccessControlCommand 42 | XCSourceEditorCommandIdentifier 43 | $(PRODUCT_BUNDLE_IDENTIFIER).DecreaseAccess 44 | XCSourceEditorCommandName 45 | Decrement Access Levels 46 | 47 | 48 | XCSourceEditorCommandClassName 49 | $(PRODUCT_MODULE_NAME).AccessControlCommand 50 | XCSourceEditorCommandIdentifier 51 | $(PRODUCT_BUNDLE_IDENTIFIER).MakeAPI 52 | XCSourceEditorCommandName 53 | Create API (internal > public) 54 | 55 | 56 | XCSourceEditorCommandClassName 57 | $(PRODUCT_MODULE_NAME).AccessControlCommand 58 | XCSourceEditorCommandIdentifier 59 | $(PRODUCT_BUNDLE_IDENTIFIER).RemoveAPI 60 | XCSourceEditorCommandName 61 | Remove API (public > internal) 62 | 63 | 64 | XCSourceEditorCommandClassName 65 | $(PRODUCT_MODULE_NAME).AccessControlCommand 66 | XCSourceEditorCommandIdentifier 67 | $(PRODUCT_BUNDLE_IDENTIFIER).MakePublic 68 | XCSourceEditorCommandName 69 | All public 70 | 71 | 72 | XCSourceEditorCommandClassName 73 | $(PRODUCT_MODULE_NAME).AccessControlCommand 74 | XCSourceEditorCommandIdentifier 75 | $(PRODUCT_BUNDLE_IDENTIFIER).MakeInternal 76 | XCSourceEditorCommandName 77 | All internal 78 | 79 | 80 | XCSourceEditorCommandClassName 81 | $(PRODUCT_MODULE_NAME).AccessControlCommand 82 | XCSourceEditorCommandIdentifier 83 | $(PRODUCT_BUNDLE_IDENTIFIER).MakeFileprivate 84 | XCSourceEditorCommandName 85 | All fileprivate 86 | 87 | 88 | XCSourceEditorCommandClassName 89 | $(PRODUCT_MODULE_NAME).AccessControlCommand 90 | XCSourceEditorCommandIdentifier 91 | $(PRODUCT_BUNDLE_IDENTIFIER).MakePrivate 92 | XCSourceEditorCommandName 93 | All private 94 | 95 | 96 | XCSourceEditorCommandClassName 97 | $(PRODUCT_MODULE_NAME).AccessControlCommand 98 | XCSourceEditorCommandIdentifier 99 | $(PRODUCT_BUNDLE_IDENTIFIER).Remove 100 | XCSourceEditorCommandName 101 | Remove Access Levels 102 | 103 | 104 | XCSourceEditorExtensionPrincipalClass 105 | $(PRODUCT_MODULE_NAME).AccessControlCommand 106 | 107 | NSExtensionPointIdentifier 108 | com.apple.dt.Xcode.extension.source-editor 109 | 110 | NSHumanReadableCopyright 111 | Copyright © 2018-9 Zoe Smith 112 | 113 | 114 | -------------------------------------------------------------------------------- /AccessControlKittyTests/AccessControlKittyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessControlKittyTests.swift 3 | // AccessControlKittyTests 4 | // 5 | // Created by Zoe Smith on 16/2/19. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import XCTest 10 | import XcodeKit 11 | 12 | class AccessControlKittyTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testEntireLineSelected() { 23 | // - #0 24 | let range = XCSourceTextRange(start: XCSourceTextPosition(line: 49, column: 0), end: XCSourceTextPosition(line: 50, column: 0)) 25 | let expected = [49] 26 | XCTAssertEqual(lines(range, totalLinesInBuffer: 50), expected) 27 | } 28 | 29 | func testPartialSingleLineSelected() { 30 | // 31 | let range = XCSourceTextRange(start: XCSourceTextPosition(line: 49, column: 0), end: XCSourceTextPosition(line: 49, column: 33)) 32 | let expected = [49] 33 | XCTAssertEqual(lines(range, totalLinesInBuffer: 50), expected) 34 | } 35 | 36 | func testEntireSourceFileSelected() { 37 | // #0 54 | let range = XCSourceTextRange(start: XCSourceTextPosition(line: 49, column: 0), end: XCSourceTextPosition(line: 52, column: 34)) 55 | let expected = [49, 50, 51, 52] 56 | XCTAssertEqual(lines(range, totalLinesInBuffer: 50), expected) 57 | } 58 | 59 | func testColumnarSelections() { 60 | /* ▿ 3 elements 61 | - #0 62 | - super: NSObject 63 | - #1 64 | - super: NSObject 65 | - #2 66 | - super: NSObject 67 | Line count: 82 68 | */ 69 | let ranges = [XCSourceTextRange(start: XCSourceTextPosition(line: 71, column: 15), end: XCSourceTextPosition(line: 71, column: 23)), 70 | XCSourceTextRange(start: XCSourceTextPosition(line: 72, column: 15), end: XCSourceTextPosition(line: 72, column: 23)), 71 | XCSourceTextRange(start: XCSourceTextPosition(line: 73, column: 15), end: XCSourceTextPosition(line: 73, column: 23))] 72 | let expected = [71, 72, 73] 73 | XCTAssertEqual(ranges.flatMap { lines($0, totalLinesInBuffer: 82) }, expected) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /AccessControlKittyTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0.3 19 | CFBundleVersion 20 | 9 21 | 22 | 23 | -------------------------------------------------------------------------------- /AccessControlWorkspace.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /AccessControlWorkspace.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018-19 Zoë Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AccessControlKitty 2 | Xcode extension to change the access control level of Swift code selection 3 | 4 | [Download from the Mac App Store](https://itunes.apple.com/us/app/accesscontrolkitty/id1450391666?mt=12) – **versions have now diverged – this repo contains the latest fixes** 5 | 6 | ### Features 7 | - Works on selected Swift code to switch between `public`, `private`, `fileprivate`, `internal` or no access control modifier. Choose an option from the new Access Level of Selection item at the bottom of Xcode's Editor menu: 8 | - Increment access levels in selected code. So, `private` and `fileprivate` code becomes `internal`, `internal` becomes `public`, and any `public` code stays as is. 9 | - Decrement access levels. `private` code stays as is, `fileprivate` and `internal` become `private`, and `public` code becomes `internal`. 10 | - Create API – changes all `internal` code to `public`, exposing it as API for your framework 11 | - Remove API – similarly, changes all `public` code to be `internal`, removing its visibility as API 12 | - Set all appropriate access modifiers to one level 13 | - Remove access notation entirely 14 | - Setters with overriden access levels (for example, `private(set) internal var`) are treated separately: when incrementing/decrementing access, or making/removing API, overridden setters maintain their current access level. If the underlying entity ends up with the same access level as the overriden setter, the explicit override is removed. When setting code to a single access level, the explicit setter override is removed so the entire entity is set to the desired level. 15 | 16 | ### Caveats 17 | - It’s not particularly smart, so for example it doesn’t know if a function can’t be made public because it relies on an internal type. Or if a subclass can't be made public because its superclass isn't public. And it certainly can't reason about anything going on in any other file. It just operates on the bits of selected Swift code that *could*, grammatically speaking, have an access control modifier. 18 | - It also doesn’t support `open` or `final` for the moment, mostly because it’s a bit more work and just ship it already, and partly because I sort of feel those notations should require a bit more forethought when planning a framework. 19 | - **Next on the list are the (new I think) warnings in Swift 5 about redundant `public` declarations of top level members in `public` extensions. Work in progress, to come shortly 😅** 20 | 21 | ### To install: 22 | [Available to download now on the Mac App Store](https://itunes.apple.com/us/app/accesscontrolkitty/id1450391666?mt=12) – **versions have now diverged – this repo contains the latest fixes** 23 | 24 | The MAS and github versions are currently in sync woohoo! But if you want to install yourself: 25 | 26 | - Download the Xcode project 27 | - Archive the Mac app target 28 | - From the Organizer window, which should open automatically, click `Distribute` and export the created archive using the option `Copy` to use locally. Save wherever you like. 29 | - Launch the app 30 | - The extension will now be available in System Preferences, under the Extensions pane, listed as an Xcode Source Editor extension. Activate! 31 | - After an Xcode restart, find it under the Editor menu - it only works on selected Swift code 32 | - For even more radness, you can bind keyboard shortcuts to the menu commands 33 | 34 | ### Bugs & feedback: 35 | - Please [create an issue](https://github.com/zoejessica/AccessControlKitty/issues/new) or tweet [@zoejessica](https://twitter.com/zoejessica). 36 | -------------------------------------------------------------------------------- /SwiftLineParser/Access.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Access.swift 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 6/2/19. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum AccessChange { 12 | case singleLevel(Access) 13 | case increaseAccess 14 | case decreaseAccess 15 | case makeAPI 16 | case removeAPI 17 | } 18 | 19 | public enum Access: String { 20 | case `public` = "public" 21 | case `private` = "private" 22 | case `internal` = "internal" 23 | case `fileprivate` = "fileprivate" 24 | case remove = "" 25 | case `open` = "open" 26 | } 27 | 28 | extension Access: Comparable { 29 | 30 | public static func <(_ lhs: Access, _ rhs: Access) -> Bool { 31 | return lhs.order < rhs.order 32 | } 33 | 34 | var order: Int { 35 | switch self { 36 | case .private: return -2 37 | case .fileprivate: return -1 38 | case .internal: return 0 39 | case .remove: return 0 40 | case .public: return 1 41 | case .open: return 2 42 | } 43 | } 44 | } 45 | 46 | extension Access { 47 | init?(_ keyword: Keyword?) { 48 | guard let keyword = keyword else { 49 | return nil 50 | } 51 | 52 | if let a = Access.init(rawValue: keyword.rawValue) { 53 | self = a 54 | } else if keyword == .privateset { self = .private } 55 | else if keyword == .fileprivateset { self = .fileprivate } 56 | else if keyword == .internalset { self = .internal } 57 | else { return nil } 58 | } 59 | } 60 | 61 | extension Access { 62 | var setterString: String? { 63 | switch self { 64 | case .private: 65 | return Keyword.privateset.rawValue 66 | case .internal: 67 | return Keyword.internalset.rawValue 68 | case .fileprivate: 69 | return Keyword.fileprivateset.rawValue 70 | default: return nil 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftLineParser/Comments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comments.swift 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 4/23/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | private let singleLineComment = try! NSRegularExpression(pattern: "^//", options: []) 12 | private let multiLineCommentStart = try! NSRegularExpression(pattern: "", options: []) 13 | 14 | extension Lexer { 15 | 16 | func hasMultilineCommentPrefix(slice: Substring) -> Substring.Index? { 17 | if slice.hasPrefix("/*") { 18 | var newStartRange = slice.startIndex 19 | slice.formIndex(&newStartRange, offsetBy: 2) 20 | return newStartRange 21 | } else { 22 | return nil 23 | } 24 | } 25 | 26 | func isComment(slice: Substring) throws -> () { 27 | if matches(slice, regex: singleLineComment) { 28 | throw LexerError.singleLineComment([]) 29 | } 30 | } 31 | 32 | func trimLineAfterEndMultilineComment(line: String) -> String? { 33 | if let firstOccurence = line.range(of: "*/") { 34 | return String(line.suffix(from: firstOccurence.upperBound)) 35 | } else { 36 | return nil 37 | } 38 | } 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /SwiftLineParser/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.3 19 | CFBundleVersion 20 | 9 21 | NSHumanReadableCopyright 22 | Copyright © 2018-9 Zoë Smith 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftLineParser/Lexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Lexer.swift 3 | // Temporary 4 | // 5 | // Created by Zoe Smith on 4/20/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | 10 | // Helped a lot by a series of blog posts by Harlan Haskins on lexing and parsing in Swift: 11 | // https://harlanhaskins.com/2017/01/08/building-a-compiler-with-swift-in-llvm-part-1-introduction-and-the-lexer.html 12 | 13 | import Foundation 14 | 15 | protocol WordTag {} 16 | typealias Word = Tagged 17 | 18 | public class Lexer { 19 | 20 | var insideMultilineComment: Bool = false 21 | var insideMultilineString: Bool = false 22 | 23 | func analyse(_ line: String) -> [Token] { 24 | 25 | 26 | var line = line 27 | 28 | // Check to see if the line does include an end of multiline comment 29 | // If so trim the line of its comment prefix and continue 30 | // Otherwise the line can be ignored, return an empty token array for this line 31 | if insideMultilineComment { 32 | if let newLine = trimLineAfterEndMultilineComment(line: line) { 33 | line = newLine 34 | insideMultilineComment = false 35 | } else { 36 | return [] 37 | } 38 | } 39 | 40 | // Check for multiline string: 41 | if !insideMultilineString, let range = match(line, with: multiLineRegex) { 42 | if insideMultilineString { 43 | insideMultilineString = false 44 | return [] 45 | } else { 46 | line = String(line.prefix(upTo: range.lowerBound)) 47 | insideMultilineString = true 48 | } 49 | } 50 | 51 | // Remove single line strings: 52 | while let match = match(line, with: stringRegex) { 53 | line.removeSubrange(match) 54 | } 55 | 56 | let trimmedAndSeparatedWords = line.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: .whitespacesAndNewlines).map { Word(rawValue: $0) } 57 | var lineTokens: [Token] = [] 58 | tokenisingLine: for word in trimmedAndSeparatedWords { 59 | 60 | do { 61 | lineTokens += try tokenise(word) 62 | } catch LexerError.singleLineComment(let tokens) { 63 | lineTokens += tokens 64 | break tokenisingLine 65 | } catch { 66 | fatalError("unhandled error") 67 | } 68 | } 69 | return lineTokens 70 | } 71 | 72 | func tokenise(_ word: Word) throws -> [Token] { 73 | let s = word.rawValue 74 | if !insideMultilineComment, let token = Token(SingleCharacter.init(rawValue: s)) ?? Token(Keyword.init(rawValue: s)) { 75 | return [token] 76 | } else { 77 | return try readByCharacters(word) 78 | } 79 | } 80 | 81 | func readByCharacters(_ word: Word) throws -> [Token] { 82 | var tokens: [Token] = [] 83 | let string = word.rawValue 84 | var startIndex: String.Index = string.startIndex 85 | 86 | while startIndex < string.endIndex { 87 | do { 88 | let result = try read(slice: string[startIndex.. (token: Token?, newStartIndex: Substring.Index) { 103 | var startIndex = slice.startIndex 104 | 105 | // Ignore rest of slice if it starts with a multiline comment 106 | if let indexAfterComment = hasMultilineCommentPrefix(slice: slice) { 107 | insideMultilineComment = true 108 | return (nil, indexAfterComment) 109 | } 110 | 111 | if insideMultilineComment { 112 | // If we are in a multiline comment, but it's terminated within this slice 113 | // ignore the characters before the termination and then continue to read by character: 114 | if let termination = slice.range(of: "*/") { 115 | startIndex = termination.upperBound 116 | insideMultilineComment = false 117 | return (nil, startIndex) 118 | 119 | // If however the multiline comment does not terminate 120 | // the whole word should be ignored: 121 | } else { 122 | return (nil, slice.endIndex) 123 | } 124 | } 125 | 126 | // Check to see if the next characters are a comment: 127 | if !insideMultilineComment { try isComment(slice: slice) } 128 | 129 | // Return a token if found 130 | if let result = readIdentifier(slice: slice) { 131 | return (result.token, result.newStartIndex) 132 | } 133 | 134 | // Unknown first character - advance the index and return a nil token 135 | slice.formIndex(after: &startIndex) 136 | return (nil, startIndex) 137 | } 138 | 139 | func readIdentifier(slice: Substring) -> (token: Token, newStartIndex: Substring.Index)? { 140 | let string = String(slice) 141 | for (pattern, generator) in tokenList { 142 | if let matchRange = match(string, pattern: pattern) { 143 | let match = string[matchRange] 144 | if let token = generator(String(match)), 145 | let newStartIndex = slice.range(of: match)?.upperBound { 146 | return (token, newStartIndex) 147 | } 148 | } 149 | } 150 | return nil 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /SwiftLineParser/LineChange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineChange.swift 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 13/2/19. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | struct LineChange { 12 | let type: LineChangeType 13 | let cursor: String 14 | 15 | enum LineChangeType: Equatable { 16 | case substitute 17 | case setterSubstitute(Access) // records the access level of the setter 18 | case setterPostfix(Access) // records the access level of the setter 19 | case prefix 20 | case postfix 21 | case none 22 | } 23 | } 24 | 25 | extension LineChange { 26 | init(_ type: LineChangeType, at cursor: String) { 27 | self = LineChange.init(type: type, cursor: cursor) 28 | } 29 | 30 | init(_ type: LineChangeType, at keyword: Keyword) { 31 | self = LineChange.init(type: type, cursor: keyword.rawValue) 32 | } 33 | 34 | func substitution(target access: Access) -> String { 35 | switch access { 36 | case (.internal): return "" 37 | default: return access.rawValue 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SwiftLineParser/Logging.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logging.swift 3 | // Temporary 4 | // 5 | // Created by Zoe Smith on 4/21/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Token: CustomDebugStringConvertible { 12 | var debugDescription: String { 13 | switch self { 14 | case .keyword(let k): return k.rawValue 15 | case .singleCharacter(let s): return s.rawValue 16 | case .identifier(let i): return i 17 | case .attribute: return "attribute" 18 | } 19 | } 20 | } 21 | 22 | extension SingleCharacter: CustomDebugStringConvertible { 23 | var debugDescription: String { return rawValue } 24 | } 25 | 26 | extension Keyword: CustomDebugStringConvertible { 27 | var debugDescription: String { return rawValue } 28 | } 29 | 30 | extension Declaration: CustomDebugStringConvertible { 31 | var debugDescription: String { 32 | let brace = openBrace ? "{" : "" 33 | return "\(keyword?.rawValue ?? "")—\(brace)" 34 | } 35 | } 36 | 37 | extension Structure: CustomDebugStringConvertible { 38 | var debugDescription: String { 39 | let d = declarations.map { $0.debugDescription }.joined(separator: " ") 40 | return "\(currentLevel): \(d)" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SwiftLineParser/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 4/21/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Parser { 12 | 13 | public init(lines: [String]) { 14 | self.lines = lines 15 | let lexer = Lexer() 16 | tokens = lines.map { lexer.analyse($0) } 17 | lineIsPrefixable = Array.init(repeating: true, count: lines.count) 18 | } 19 | 20 | public func newLines(at lineNumbersToAlter: [Int], accessChange: AccessChange) -> [Int : String] { 21 | 22 | var newLines: [Int : String] = [:] 23 | 24 | for (lineNumber, linetokens) in tokens.enumerated() { 25 | 26 | let (lineChange, isPrefixable, intermediateStructure) = parseLine(in: structure, lineNumber, linetokens) 27 | self.structure = intermediateStructure 28 | lineIsPrefixable[lineNumber] = isPrefixable 29 | 30 | guard lineNumbersToAlter.contains(lineNumber), lineIsPrefixable[lineNumber] else { 31 | continue 32 | } 33 | 34 | let unmodifiedLine = lines[lineNumber] 35 | 36 | let (newLineChange, substitution, target) = resolvedLineChange(previous: lineChange, accessChange, in: structure) 37 | let changedLine = unmodifiedLine.modifyingAccess(newLineChange, with: substitution) 38 | 39 | // Further alter the line for setter overrides 40 | let setterChangedLine = changedLine.modifyingSetter(newLineChange, accessChange, targetLevel: target) 41 | newLines[lineNumber] = setterChangedLine 42 | } 43 | return newLines 44 | } 45 | 46 | // Overrides type of line change according to the particular menu command 47 | // E.g. a fileprivate entity does not change when executing the Make API command 48 | private func resolvedLineChange(previous line: LineChange, _ accessChange: AccessChange, in structure: Structure) -> (LineChange, String, Access) { 49 | 50 | 51 | let currentLevel = structure.currentLevel 52 | let noSubstitution = (LineChange(type: .none, cursor: ""), "", currentLevel) 53 | let internalString = "" 54 | 55 | switch accessChange { 56 | case .singleLevel(let level): return (line, level.rawValue, level) 57 | 58 | case .makeAPI: 59 | switch currentLevel { 60 | case nil, .internal: return (line, line.substitution(target: .public), .public) 61 | default: return noSubstitution 62 | } 63 | 64 | case .removeAPI: 65 | switch currentLevel { 66 | case .public: return (line, line.substitution(target: .internal), .internal) 67 | default: return noSubstitution 68 | } 69 | 70 | case .increaseAccess: 71 | switch currentLevel { 72 | case .public: return noSubstitution 73 | case .internal, nil: return (line, line.substitution(target: .public), .public) 74 | case .fileprivate: return (line, line.substitution(target: .internal), .internal) 75 | case .private: return (line, line.substitution(target: .internal), .internal) 76 | default: fatalError() 77 | } 78 | 79 | case .decreaseAccess: 80 | switch currentLevel { 81 | case .public: return (line, internalString, .internal) 82 | case .internal, nil: return (line, line.substitution(target: .private), .private) 83 | case .fileprivate: return (line, line.substitution(target: .private), .private) 84 | case .private: return noSubstitution 85 | default: fatalError() 86 | } 87 | } 88 | } 89 | 90 | private var lines: [String] 91 | private var tokens: [[Token]] 92 | 93 | private var lineIsPrefixable: [Bool] // Overrides lineChangeType: if lineIsPrefixable == false, lineChangeType is ignored 94 | // private var lineChangeType: [Int : LineChange] = [:] 95 | 96 | var structure = Structure() 97 | } 98 | 99 | 100 | extension Parser { 101 | private func parseLine(in structure: Structure, _ line: Int, _ lineTokens: [Token]) -> (LineChange, Bool, Structure) { 102 | 103 | var structure = structure 104 | var lineIsPrefixable = false 105 | var lineChange: LineChange = LineChange(.none, at: "") // default if tokens are empty 106 | 107 | lineIsPrefixable = structure.allowsInternalAccessControlModifiers 108 | 109 | guard let firstToken = lineTokens.first else { 110 | return (lineChange, lineIsPrefixable, structure) 111 | } 112 | 113 | if !firstToken.isAccessControlModifiableInFirstPosition || 114 | structure.tokens.containExtensionWithConformance { 115 | lineIsPrefixable = false 116 | structure.build(with: lineTokens) 117 | return (lineChange, lineIsPrefixable, structure) 118 | } 119 | 120 | // If there is a setter access keyword, it's a setter subsitutiton, check it 121 | // before general line tokens because you need to check whether it's a 122 | // fileprivate(set) public var ... for example 123 | if let setterKeyword = lineTokens.containSetterAccessKeyword, 124 | let setter = Access(setterKeyword) { 125 | 126 | if let accessKeyword = lineTokens.containAccessKeyword { 127 | lineChange = LineChange(.setterSubstitute(setter), at: accessKeyword) 128 | } else { 129 | lineChange = LineChange(.setterPostfix(setter), at: setterKeyword) 130 | } 131 | } 132 | 133 | // If any token on the line contains an access keyword, assume it's a substution: 134 | else if let accessKeyword = lineTokens.containAccessKeyword { 135 | lineChange = LineChange(.substitute, at: accessKeyword) 136 | 137 | } else { 138 | 139 | switch firstToken { 140 | 141 | case .keyword(let keyword) where operatorFixKeywords.contains(keyword) 142 | && lineTokens.dropFirst().first != nil 143 | && Token.keyword(.operator) == lineTokens.dropFirst().first!: 144 | 145 | lineIsPrefixable = false 146 | 147 | case .keyword(let keyword) where postfixableFunctionKeywords.contains(keyword): 148 | lineChange = LineChange(.postfix, at: keyword) 149 | 150 | case .attribute(let attribute) where Array(lineTokens.dropFirst()).containAnyKeyword == true: 151 | lineChange = LineChange(.postfix, at: attribute) 152 | 153 | case .attribute: 154 | lineChange = LineChange(.none, at: "") 155 | 156 | case .keyword(let keyword): 157 | lineChange = LineChange(.prefix, at: keyword) 158 | 159 | default: break 160 | } 161 | } 162 | 163 | structure.build(with: lineTokens) 164 | 165 | if lineTokens.containExtensionWithConformance { 166 | lineIsPrefixable = false 167 | } 168 | 169 | return (lineChange, lineIsPrefixable, structure) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /SwiftLineParser/Regex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Regex.swift 3 | // Temporary 4 | // 5 | // Created by Zoe Smith on 4/20/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | public func match(_ string: String, pattern: String) -> Range? { 12 | let regex = try! NSRegularExpression(pattern: pattern, options: []) 13 | let firstMatch = match(string, with: regex) 14 | return firstMatch 15 | } 16 | 17 | public func match(_ string: String, with regex: NSRegularExpression) -> Range? { 18 | let firstMatch = regex.rangeOfFirstMatch(in: string, options: [], range: NSMakeRange(0, string.utf16.count)) 19 | guard firstMatch.location != NSNotFound else { return nil } 20 | guard firstMatch.lowerBound != firstMatch.upperBound else { 21 | return nil 22 | } 23 | let range = Range(firstMatch, in: string) 24 | return range 25 | } 26 | 27 | public func matches(_ string: String, with regex: NSRegularExpression) -> [Range]? { 28 | let matches = regex.matches(in: string, options: [], range: NSMakeRange(0, string.utf16.count)) 29 | let ranges = matches.map { Range($0.range, in: string) }.compactMap { $0 } 30 | guard ranges.count > 0 else { return nil } 31 | return ranges 32 | } 33 | 34 | func matches(_ slice: Substring, regex: NSRegularExpression) -> Bool { 35 | return match(String(slice), with: regex) != nil 36 | } 37 | 38 | typealias TokenGenerator = (String) -> Token? 39 | let tokenList: [(String, TokenGenerator)] = [ 40 | ("^[^a-zA-Z0-9]", { Token(SingleCharacter.init(rawValue: $0)) }), 41 | ("^@[a-zA-Z0-9]*", { .attribute($0) }), 42 | ("^[a-zA-Z0-9]*", { Token(Keyword.init(rawValue: $0)) ?? .identifier($0) }), 43 | ("[.]init", { Token.identifier($0) }) 44 | ] 45 | 46 | // .*? means match any character as few times as possible 47 | let stringRegex = try! NSRegularExpression(pattern: "\".*?\"[^\"]", options: []) 48 | let multiLineRegex = try! NSRegularExpression(pattern: "\"\"\"\n", options: []) 49 | -------------------------------------------------------------------------------- /SwiftLineParser/String alterations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String alterations.swift 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 13/2/19. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | func modifyingAccess(_ change: LineChange, with substitution: String) -> String { 14 | 15 | var line = self 16 | 17 | var searchWord = change.cursor 18 | 19 | // These two cases might be fixed by using remove function on String 20 | if substitution == "" { 21 | switch change.type { 22 | case .substitute, .setterSubstitute: 23 | searchWord = searchWord + " " 24 | default: break 25 | } 26 | } 27 | 28 | guard let range = line.range(of: searchWord) else { 29 | return self 30 | } 31 | 32 | switch change.type { 33 | case .none: return self 34 | case .substitute, .setterSubstitute: 35 | return line.replacingCharacters(in: range, with: substitution) 36 | case .postfix where substitution != "", .setterPostfix where substitution != "": 37 | line.insert(contentsOf: " \(substitution)", at: range.upperBound) 38 | return line 39 | case .prefix where substitution != "": 40 | line.insert(contentsOf: "\(substitution) ", at: range.lowerBound) 41 | return line 42 | default: 43 | return line 44 | } 45 | } 46 | 47 | func modifyingSetter(_ line: LineChange, _ accessChange: AccessChange, targetLevel: Access) -> String { 48 | switch line.type { 49 | case .setterPostfix(setterAccess: let currentSetterAccess): 50 | return self.modifyingSetterForTarget(current: currentSetterAccess, access: accessChange, targetLevel: targetLevel) 51 | case .setterSubstitute(setterAccess: let currentSetterAccess): 52 | return self.modifyingSetterForTarget(current: currentSetterAccess, access: accessChange, targetLevel: targetLevel) 53 | default: return self 54 | } 55 | } 56 | 57 | private func modifyingSetterForTarget(current setter: Access, access: AccessChange, targetLevel: Access) -> String { 58 | 59 | guard setter != targetLevel else { 60 | return self.removingSetter(setter) 61 | } 62 | 63 | switch access { 64 | 65 | case .increaseAccess: 66 | return self 67 | 68 | case .decreaseAccess: 69 | 70 | switch setter { 71 | case .fileprivate where targetLevel == .private: return self.removingSetter(setter) 72 | case .fileprivate, .internal: return self 73 | default: return self 74 | } 75 | 76 | case .makeAPI: 77 | return self 78 | 79 | case .removeAPI: 80 | switch setter { 81 | case .internal: return self.alteringSetter(setter, target: .internal) 82 | default: return self 83 | } 84 | 85 | case .singleLevel: 86 | return self.removingSetter(setter) 87 | 88 | } 89 | } 90 | 91 | private func alteringSetter(_ setter: Access, target: Access) -> String { 92 | guard let setterString = setter.setterString, target <= .internal, let targetString = target.setterString else { 93 | return self 94 | } 95 | 96 | guard setter != target else { 97 | return removingSetter(setter) 98 | } 99 | 100 | guard let range = range(of: setterString) else { 101 | return self 102 | } 103 | 104 | return replacingCharacters(in: range, with: targetString) 105 | } 106 | 107 | private func removingSetter(_ setter: Access) -> String { 108 | guard let setterString = setter.setterString else { return self } 109 | guard let range = range(of: setterString) else { return self } 110 | let rangeWithFollowingSpace = self.range(of: (setterString + " ")) ?? range 111 | var copy = self 112 | copy.removeSubrange(rangeWithFollowingSpace) 113 | return copy 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /SwiftLineParser/Structure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Structure.swift 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 2/2/19. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | struct Declaration: Equatable { 13 | let access: Access? 14 | let keyword: Keyword? 15 | var openBrace: Bool 16 | } 17 | 18 | extension Declaration { 19 | var isVariableWithoutClosure: Bool { 20 | if openBrace == false && (keyword == .var || keyword == .let) { 21 | return true 22 | } else { 23 | return false 24 | } 25 | } 26 | 27 | var isVariableWithClosure: Bool { 28 | if openBrace == true && (keyword == .var || keyword == .let) { 29 | return true 30 | } else { 31 | return false 32 | } 33 | } 34 | } 35 | 36 | struct Structure: Equatable { 37 | 38 | init() { 39 | self.declarations = [] 40 | } 41 | 42 | var currentLevel: Access { 43 | get { 44 | if let explicitAccess = declarations.last?.access { 45 | return explicitAccess 46 | } else if let implicitAccess = declarations.last(where: { $0.access != nil })?.access { 47 | return min(implicitAccess, Access.internal) // implict access levels even in public entity is always internal 48 | } 49 | return .internal // default 50 | } 51 | } 52 | 53 | private(set) var declarations: [Declaration] 54 | 55 | mutating func build(with lineTokens: [Token]) { 56 | 57 | var lineaccess: Access? 58 | 59 | for token in lineTokens { 60 | 61 | switch token { 62 | 63 | case .keyword(let keyword) where accessKeywords.contains(keyword): 64 | 65 | lineaccess = Access(keyword) 66 | 67 | case .keyword(let keyword) where structureKeywords.contains(keyword) && !starts(with: Keyword.protocol): 68 | 69 | if let last = declarations.last, last.isVariableWithoutClosure { 70 | _ = declarations.popLast() 71 | } 72 | 73 | declarations.append(.init(access: lineaccess, keyword: keyword, openBrace: false)) 74 | 75 | case .singleCharacter(let char) where char == .bracketOpen: 76 | openBrace(level: lineaccess) 77 | case .singleCharacter(let char) where char == .bracketClose: 78 | closeBrace() 79 | 80 | default: break 81 | 82 | } 83 | // print(debugDescription) // This is a good place to debug structure builds 84 | } 85 | } 86 | 87 | private mutating func openBrace(level: Access?) { 88 | guard var last = declarations.last, last.openBrace == false else { 89 | declarations.append(.init(access: level, keyword: nil, openBrace: true)) 90 | return 91 | } 92 | last.openBrace = true 93 | _ = declarations.popLast() 94 | declarations.append(last) 95 | } 96 | 97 | private mutating func closeBrace() { 98 | if let last = declarations.last { 99 | if last.openBrace == true { 100 | _ = declarations.popLast() 101 | } else { 102 | _ = declarations.popLast() 103 | closeBrace() 104 | } 105 | } 106 | } 107 | } 108 | 109 | extension Structure { 110 | 111 | var allowsInternalAccessControlModifiers: Bool { 112 | if contains(any: localScopeKeywords) { return false } 113 | // if contains(Keyword.protocol) { return false } 114 | if declarations.contains(where: { $0.isVariableWithClosure }) { return false } 115 | return true 116 | } 117 | 118 | var openStructures: Int { 119 | return declarations.filter { $0.openBrace == true }.count 120 | } 121 | 122 | var tokens: [Token] { 123 | return declarations.compactMap { 124 | if let keyword = $0.keyword { 125 | return Token.keyword(keyword) 126 | } else { 127 | return nil 128 | } 129 | } 130 | } 131 | 132 | func contains(_ element: Keyword) -> Bool { 133 | return declarations.contains(where: { $0.keyword == element }) 134 | } 135 | 136 | func contains(any elements: [Keyword]) -> Bool { 137 | for keyword in elements { 138 | if contains(keyword) { 139 | return true 140 | } 141 | } 142 | return false 143 | } 144 | 145 | func contains(_ declaration: Declaration) -> Bool { 146 | return declarations.contains(declaration) 147 | } 148 | 149 | func starts(with keyword: Keyword) -> Bool { 150 | if let first = declarations.first, first.keyword == keyword { 151 | return true 152 | } else { 153 | return false 154 | } 155 | } 156 | 157 | func starts(with declaration: Declaration) -> Bool { 158 | if let first = declarations.first, first == declaration { 159 | return true 160 | } else { 161 | return false 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /SwiftLineParser/SwiftLineParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftLineParser.h 3 | // SwiftLineParser 4 | // 5 | // Created by Zoe Smith on 4/28/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftLineParser. 12 | FOUNDATION_EXPORT double SwiftLineParserVersionNumber; 13 | 14 | //! Project version string for SwiftLineParser. 15 | FOUNDATION_EXPORT const unsigned char SwiftLineParserVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftLineParser/Tagged.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tagged.swift 3 | // Temporary 4 | // 5 | // Created by Zoe Smith on 4/19/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | // Uses ideas from https://github.com/pointfreeco/swift-tagged 12 | 13 | public struct Tagged { 14 | public let rawValue: RawValue 15 | } 16 | 17 | extension Tagged: Equatable where RawValue: Equatable { 18 | public static func ==(lhs: Tagged, rhs: Tagged) -> Bool { 19 | return lhs.rawValue == rhs.rawValue 20 | } 21 | } 22 | 23 | extension Tagged: ExpressibleByUnicodeScalarLiteral where RawValue: ExpressibleByUnicodeScalarLiteral { 24 | public typealias UnicodeScalarLiteralType = RawValue.UnicodeScalarLiteralType 25 | public init(unicodeScalarLiteral value: RawValue.UnicodeScalarLiteralType) { 26 | self.init(rawValue: RawValue(unicodeScalarLiteral: value)) 27 | } 28 | } 29 | 30 | extension Tagged: ExpressibleByExtendedGraphemeClusterLiteral where RawValue: ExpressibleByExtendedGraphemeClusterLiteral { 31 | public typealias ExtendedGraphemeClusterLiteralType = RawValue.ExtendedGraphemeClusterLiteralType 32 | public init(extendedGraphemeClusterLiteral value: RawValue.ExtendedGraphemeClusterLiteralType) { 33 | self.init(rawValue: RawValue(extendedGraphemeClusterLiteral: value)) 34 | } 35 | } 36 | 37 | extension Tagged: ExpressibleByStringLiteral where RawValue: ExpressibleByStringLiteral { 38 | public typealias StringLiteralType = RawValue.StringLiteralType 39 | public init(stringLiteral value: StringLiteralType) { 40 | self.init(rawValue: RawValue(stringLiteral: value)) 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /SwiftLineParser/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // Temporary 4 | // 5 | // Created by Zoe Smith on 4/20/18.`while` 'repeat` 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Token: Equatable { 12 | case attribute(String) 13 | case keyword(Keyword) 14 | case singleCharacter(SingleCharacter) 15 | case identifier(String) 16 | } 17 | 18 | enum SingleCharacter: String, CaseIterable { 19 | case bracketOpen = "{", bracketClose = "}" 20 | case colon = ":" 21 | case equals = "=" 22 | } 23 | 24 | enum Keyword: String, CaseIterable { 25 | case `protocol`, `extension`, `struct`, `class`, `let`, `var`, 26 | `public`, `private`, `open`, `fileprivate`, `internal`, 27 | `override`, `func`, 28 | `final`, `enum`, `case`, 29 | _init = "init", 30 | `static`, `typealias`, `required`, 31 | `mutating`, `nonmutating`, 32 | `for`, `while`, `repeat`, 33 | `unowned`, unownedsafe = "unowned(safe)", unownedunsafe = "unowned(unsafe)", 34 | `convenience`, `do`, `catch`, `defer`, `subscript`, 35 | `prefix`, `postfix`, `infix`, 36 | `lazy`, `weak`, 37 | privateset = "private(set)", fileprivateset = "fileprivate(set)", internalset = "internal(set)", 38 | `operator` 39 | 40 | } 41 | 42 | let operatorFixKeywords: [Keyword] = [.prefix, .infix, .postfix] 43 | let nonAccessModifiableKeywords: [Keyword] = [.case, .for, .while, .repeat, .do, .catch, .defer] 44 | let localScopeKeywords: [Keyword] = [.func, ._init, .for, .while, .repeat, .protocol, .do, .catch, .defer, .subscript] 45 | let structureKeywords: [Keyword] = [ .protocol, .class, .struct, .enum, .extension, .func, ._init, .var, .let, .for, .while, .repeat, .do, .catch, .defer, .subscript] 46 | let accessKeywords: [Keyword] = [.public, .private, .fileprivate, .internal, .open] 47 | let postfixableFunctionKeywords: [Keyword] = [.static, .unowned, .unownedsafe, .unownedunsafe, .required, .convenience] 48 | let setterAccessKeywords: [Keyword] = [.privateset, .fileprivateset, .internalset] 49 | 50 | extension Token { 51 | init?(_ singleCharacter: SingleCharacter?) { 52 | guard singleCharacter != nil else { return nil } 53 | self = .singleCharacter(singleCharacter!) 54 | } 55 | 56 | init?(_ keyword: Keyword?) { 57 | guard keyword != nil else { return nil } 58 | self = .keyword(keyword!) 59 | } 60 | } 61 | 62 | extension Token { 63 | var isAccessControlModifiableInFirstPosition: Bool { 64 | switch self { 65 | case .singleCharacter: return false 66 | case .identifier: return false 67 | case .keyword(let keyword) where nonAccessModifiableKeywords.contains(keyword): return false 68 | default: return true 69 | } 70 | } 71 | } 72 | 73 | extension Array where Element == Token { 74 | 75 | var containAccessKeyword: Keyword? { 76 | for token in self { 77 | if case let Token.keyword(keyword) = token, accessKeywords.contains(keyword) { 78 | return keyword 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | var containSetterAccessKeyword: Keyword? { 85 | for token in self { 86 | if case let Token.keyword(keyword) = token, setterAccessKeywords.contains(keyword) { 87 | return keyword 88 | } 89 | } 90 | return nil 91 | } 92 | 93 | var containAnyKeyword: Bool { 94 | for token in self { 95 | if case Token.keyword(_) = token { 96 | return true 97 | } 98 | } 99 | return false 100 | } 101 | 102 | var containExtensionWithConformance: Bool { 103 | guard let startIndex = self.index(of: Token.keyword(.extension)) else { return false } 104 | var remainingTokens = self.dropFirst(startIndex + 1) 105 | guard self.count >= 3 else { return false } 106 | let first = remainingTokens.removeFirst() 107 | guard case Token.identifier = first else { return false } 108 | let second = remainingTokens.removeFirst() 109 | guard case Token.singleCharacter(.colon) = second else { return false } 110 | let third = remainingTokens.removeFirst() 111 | guard case Token.identifier = third else { return false } 112 | return true 113 | } 114 | } 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /SwiftLineParserTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0.3 19 | CFBundleVersion 20 | 9 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftLineParserTests/LexerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemporaryTests.swift 3 | // TemporaryTests 4 | // 5 | // Created by Zoe Smith on 4/20/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import XCTest 10 | @testable import SwiftLineParser 11 | 12 | class LexerTests: XCTestCase { 13 | 14 | var lexer: Lexer! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | lexer = Lexer() 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | super.tearDown() 25 | } 26 | 27 | func testOutput() { 28 | let line = "public protocol foo {" 29 | let expectedOutput: [Token] = [.keyword(.public), .keyword(.protocol), .identifier("foo"), .singleCharacter(.bracketOpen)] 30 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 31 | } 32 | 33 | func testOutputBracketSpacingA() { 34 | let line = "public protocol foo{" 35 | let expectedOutput: [Token] = [.keyword(.public), .keyword(.protocol), .identifier("foo"), .singleCharacter(.bracketOpen)] 36 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 37 | } 38 | 39 | func testOutputBracketSpacingB() { 40 | let line = "{protocol}. ." 41 | let expectedOutput: [Token] = [.singleCharacter(.bracketOpen), 42 | .keyword(.protocol), 43 | .singleCharacter(.bracketClose)] 44 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 45 | } 46 | 47 | func testOutputA() { 48 | let line = "override func setUp() {" 49 | let expectedOutput: [Token] = [.keyword(.override), 50 | .keyword(.func), 51 | .identifier("setUp"), 52 | .singleCharacter(.bracketOpen)] 53 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 54 | } 55 | 56 | func testNumber() { 57 | let line = "override func setUp() 23456 {" 58 | let expectedOutput: [Token] = [.keyword(.override), 59 | .keyword(.func), 60 | .identifier("setUp"), 61 | .identifier("23456"), 62 | .singleCharacter(.bracketOpen)] 63 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 64 | } 65 | 66 | func testSingleLineComment() { 67 | let line = "override //func setUp() 23456 {" 68 | let expectedOutput: [Token] = [.keyword(.override)] 69 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 70 | 71 | let lineA = "override//func setUp() 23456 {" 72 | XCTAssertEqual(lexer.analyse(lineA), expectedOutput) 73 | 74 | let lineB = "override//*()(sdfgsdfrsdvfunc setUp() 23456 {" 75 | XCTAssertEqual(lexer.analyse(lineB), expectedOutput) 76 | } 77 | 78 | func testMultiLineCommentStart() { 79 | let line = "override /* func setUp() 23456 {" 80 | let expectedOutput: [Token] = [.keyword(.override)] 81 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 82 | 83 | lexer.insideMultilineComment = false 84 | let lineA = "override/*///func setUp() 23456 {" 85 | XCTAssertEqual(lexer.analyse(lineA), expectedOutput) 86 | 87 | lexer.insideMultilineComment = false 88 | let lineB = "override/*/*/*override/*()(sdfgsdfrsdvfunc setUp() 23456 {" 89 | XCTAssertEqual(lexer.analyse(lineB), expectedOutput) 90 | } 91 | 92 | func testEndMultiLineComment() { 93 | let line = "*/override /* func setUp() 23456 {" 94 | let expectedOutput: [Token] = [.keyword(.override)] 95 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 96 | XCTAssertTrue(lexer.insideMultilineComment) 97 | 98 | lexer.insideMultilineComment = false 99 | let lineA = "override/*///func setUp() 23456*/override {" 100 | let expectedOutputA: [Token] = [.keyword(.override), .keyword(.override), .singleCharacter(.bracketOpen)] 101 | XCTAssertEqual(lexer.analyse(lineA), expectedOutputA) 102 | XCTAssertFalse(lexer.insideMultilineComment) 103 | 104 | let lineB = "override/*///func setUp() 23456*/override {" 105 | let expectedOutputB: [Token] = [.keyword(.override), .keyword(.override), .singleCharacter(.bracketOpen)] 106 | XCTAssertEqual(lexer.analyse(lineB), expectedOutputB) 107 | XCTAssertFalse(lexer.insideMultilineComment) 108 | } 109 | 110 | func testString() { 111 | let line = "\"/*\" override" 112 | let expectedOutput: [Token] = [.keyword(.override)] 113 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 114 | XCTAssertFalse(lexer.insideMultilineComment) 115 | 116 | let lineA = "\" override" 117 | XCTAssertEqual(lexer.analyse(lineA), expectedOutput) 118 | XCTAssertFalse(lexer.insideMultilineComment) 119 | } 120 | 121 | func testMultipleStrings() { 122 | let line = "\"override\" func struct \"23456\" thing \"other\" thing" 123 | let expectedOutput: [Token] = [.keyword(.func), .keyword(.struct), .identifier("thing"), .identifier("thing")] 124 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 125 | XCTAssertFalse(lexer.insideMultilineComment) 126 | } 127 | 128 | func testMultilineString() { 129 | let line = "override \"\"\" func" 130 | let expectedOutput: [Token] = [.keyword(.override), .keyword(.func)] 131 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 132 | XCTAssertFalse(lexer.insideMultilineString) 133 | 134 | let lineA = "override \"\"\"\n" 135 | let expectedOutputA: [Token] = [.keyword(.override)] 136 | XCTAssertEqual(lexer.analyse(lineA), expectedOutputA) 137 | XCTAssertTrue(lexer.insideMultilineString) 138 | } 139 | 140 | func testAttribute() { 141 | let line = "public protocol @IBOutlet foo {" 142 | let expectedOutput: [Token] = [.keyword(.public), .keyword(.protocol), .attribute("@IBOutlet"), .identifier("foo"), .singleCharacter(.bracketOpen)] 143 | XCTAssertEqual(lexer.analyse(line), expectedOutput) 144 | } 145 | } 146 | 147 | //func struct "23456" thing 148 | -------------------------------------------------------------------------------- /SwiftLineParserTests/ParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParserTests.swift 3 | // SwiftLineParserTests 4 | // 5 | // Created by Zoe Smith on 4/21/18. 6 | // Copyright © 2018-9 Zoë Smith. Distributed under the MIT License. 7 | // 8 | 9 | import XCTest 10 | @testable import SwiftLineParser 11 | 12 | class ParserTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | continueAfterFailure = true 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testStructure() { 26 | let test = 27 | """ 28 | final class ViewController: NSViewController { 29 | @IBOutlet var textView: NSTextView! 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | let strings: [Highlight] = markdown() 33 | let result = strings.map { $0.rendered }.join(separator: " ") 34 | textView.textStorage!.setAttributedString(result) 35 | } 36 | } 37 | """ 38 | let expected = 39 | """ 40 | public final class ViewController: NSViewController { 41 | @IBOutlet public var textView: NSTextView! 42 | public override func viewDidLoad() { 43 | super.viewDidLoad() 44 | let strings: [Highlight] = markdown() 45 | let result = strings.map { $0.rendered }.join(separator: " ") 46 | textView.textStorage!.setAttributedString(result) 47 | } 48 | } 49 | """ 50 | multilineTest(test: test, expected: expected) 51 | 52 | } 53 | 54 | func testEnum() { 55 | let test = 56 | """ 57 | enum Kind: Int { 58 | case keyword 59 | case string 60 | case other 61 | 62 | init?(sourceKitType type: String) { 63 | switch type { 64 | case source.lang.swift.syntaxtype.keyword: self = .keyword 65 | case source.lang.swift.syntaxtype.string: self = .string 66 | default: self = .other 67 | } 68 | } 69 | } 70 | """ 71 | let expected = 72 | """ 73 | public enum Kind: Int { 74 | case keyword 75 | case string 76 | case other 77 | 78 | public init?(sourceKitType type: String) { 79 | switch type { 80 | case source.lang.swift.syntaxtype.keyword: self = .keyword 81 | case source.lang.swift.syntaxtype.string: self = .string 82 | default: self = .other 83 | } 84 | } 85 | } 86 | """ 87 | multilineTest(test: test, expected: expected) 88 | } 89 | 90 | func testFunc() { 91 | let test = 92 | """ 93 | func highlightSyntax(code: String) throws -> [(Range, Kind)] { 94 | let tempDir = NSURL(fileURLWithPath: NSTemporaryDirectory()) 95 | let tmpFile = tempDir.appendingPathComponent(UUID().uuidString + \".swift\")! 96 | try! code.write(to: tmpFile, atomically: true, encoding: .utf8)] 97 | return (range, Kind(sourceKitType: dict[\"type\"] as! String)!) 98 | """ 99 | let expected = 100 | """ 101 | public func highlightSyntax(code: String) throws -> [(Range, Kind)] { 102 | let tempDir = NSURL(fileURLWithPath: NSTemporaryDirectory()) 103 | let tmpFile = tempDir.appendingPathComponent(UUID().uuidString + \".swift\")! 104 | try! code.write(to: tmpFile, atomically: true, encoding: .utf8)] 105 | return (range, Kind(sourceKitType: dict[\"type\"] as! String)!) 106 | """ 107 | multilineTest(test: test, expected: expected) 108 | } 109 | 110 | 111 | func testPrePostSubstitutionFixing() { 112 | let test = 113 | """ 114 | public let strings: [Highlight] = markdown() 115 | final class ViewController: NSViewController { 116 | @IBOutlet var textView: NSTextView! 117 | func highlightSyntax(code: String) throws -> [(Range, Kind)] { 118 | """ 119 | let expected = 120 | """ 121 | public let strings: [Highlight] = markdown() 122 | public final class ViewController: NSViewController { 123 | @IBOutlet public var textView: NSTextView! 124 | public func highlightSyntax(code: String) throws -> [(Range, Kind)] { 125 | """ 126 | multilineTest(test: test, expected: expected) 127 | } 128 | 129 | func testPrePostSubstitutionFixingVars() { 130 | let test = 131 | """ 132 | public var strings: [Highlight] = markdown() 133 | final class ViewController: NSViewController { 134 | @IBOutlet var textView: NSTextView! 135 | func highlightSyntax(code: String) throws -> [(Range, Kind)] { 136 | """ 137 | 138 | let expected = 139 | """ 140 | public var strings: [Highlight] = markdown() 141 | public final class ViewController: NSViewController { 142 | @IBOutlet public var textView: NSTextView! 143 | public func highlightSyntax(code: String) throws -> [(Range, Kind)] { 144 | """ 145 | multilineTest(test: test, expected: expected) 146 | } 147 | 148 | func testPropertiesInsideStruct() { 149 | let test = 150 | """ 151 | struct Highlight where Result: Block & HighlightedCode { 152 | let rendered: Result 153 | """ 154 | 155 | let expected = 156 | """ 157 | public struct Highlight where Result: Block & HighlightedCode { 158 | public let rendered: Result 159 | """ 160 | multilineTest(test: test, expected: expected) 161 | } 162 | 163 | func testPropertiesMethodsInsideProtocol() { 164 | let test = 165 | """ 166 | protocol HighlightedCode { 167 | static func highlight(text: String, tokens: [(Range, Kind)]) -> Self 168 | var whatever { get } 169 | var another { get set } 170 | """ 171 | let expected = 172 | """ 173 | public protocol HighlightedCode { 174 | static func highlight(text: String, tokens: [(Range, Kind)]) -> Self 175 | var whatever { get } 176 | var another { get set } 177 | """ 178 | multilineTest(test: test, expected: expected) 179 | } 180 | 181 | func testPropertiesMethodsInsideProtocolWithClosingBrace() { 182 | let test = 183 | """ 184 | protocol HighlightedCode { 185 | static func highlight(text: String, tokens: [(Range, Kind)]) -> Self 186 | var whatever { get } 187 | var whatever { get } 188 | var whatever { get } 189 | var another { get set } 190 | } 191 | """ 192 | let expected = 193 | """ 194 | public protocol HighlightedCode { 195 | static func highlight(text: String, tokens: [(Range, Kind)]) -> Self 196 | var whatever { get } 197 | var whatever { get } 198 | var whatever { get } 199 | var another { get set } 200 | } 201 | """ 202 | multilineTest(test: test, expected: expected) 203 | } 204 | 205 | func testExtensionWithConformance() { 206 | let test = 207 | """ 208 | extension NSAttributedString: Block { 209 | static func paragraph(text: String) -> Self { 210 | return .init(string: text) 211 | } 212 | """ 213 | let expected = 214 | """ 215 | extension NSAttributedString: Block { 216 | static public func paragraph(text: String) -> Self { 217 | return .init(string: text) 218 | } 219 | """ 220 | multilineTest(test: test, expected: expected) 221 | } 222 | 223 | func testInitAsMethodNotRecognized() { 224 | let test = 225 | """ 226 | extension NSAttributedString: HighlightedCode { 227 | static func highlight(text: String, tokens: [(Range, Kind)]) -> Self { 228 | let result = NSMutableAttributedString(string: text) 229 | for highlight in tokens { 230 | let range = NSRange(highlight.0, in: text) 231 | let color = highlight.1.color 232 | result.addAttribute(.foregroundColor, value: color, range: range) 233 | } 234 | return .init(attributedString: result) 235 | } 236 | } 237 | 238 | extension NSAttributedString: Block { 239 | static func paragraph(text: String) -> Self { 240 | return .init(string: text) 241 | } 242 | 243 | static func codeBlock(text: String, language: String?) -> Self { 244 | return .init(string: text) 245 | } 246 | } 247 | """ 248 | let expected = 249 | """ 250 | extension NSAttributedString: HighlightedCode { 251 | static public func highlight(text: String, tokens: [(Range, Kind)]) -> Self { 252 | let result = NSMutableAttributedString(string: text) 253 | for highlight in tokens { 254 | let range = NSRange(highlight.0, in: text) 255 | let color = highlight.1.color 256 | result.addAttribute(.foregroundColor, value: color, range: range) 257 | } 258 | return .init(attributedString: result) 259 | } 260 | } 261 | 262 | extension NSAttributedString: Block { 263 | static public func paragraph(text: String) -> Self { 264 | return .init(string: text) 265 | } 266 | 267 | static public func codeBlock(text: String, language: String?) -> Self { 268 | return .init(string: text) 269 | } 270 | } 271 | """ 272 | multilineTest(test: test, expected: expected) 273 | } 274 | 275 | func testDoubleAttributeMarking() { 276 | let test = "@IBOutlet private var Thing : UISwitch" 277 | let expected = "@IBOutlet public var Thing : UISwitch" 278 | multilineTest(test: test, expected: expected) 279 | } 280 | 281 | func testRemoval() { 282 | let lines = ["public let strings: [Highlight] = markdown()", 283 | "public final class ViewController: NSViewController {", 284 | "@IBOutlet public var textView: NSTextView!", 285 | "public func highlightSyntax(code: String) throws -> [(Range, Kind)] {"] 286 | let expectedNewLines = ["let strings: [Highlight] = markdown()", 287 | "final class ViewController: NSViewController {", 288 | "@IBOutlet var textView: NSTextView!", 289 | "func highlightSyntax(code: String) throws -> [(Range, Kind)] {"] 290 | let parser = Parser(lines: lines) 291 | let newLines = parser.newLines(at: [0, 1, 2, 3], accessChange: .singleLevel(.remove)) 292 | for (index, expectedline) in expectedNewLines.enumerated() { 293 | XCTAssertEqual(newLines[index], expectedline, "Line no.: \(index) \(lines[index]) was incorrectly parsed") 294 | } 295 | } 296 | 297 | func testRequiredInitKeyword() { 298 | let lines = [ 299 | "required init?(coder aDecoder: NSCoder) {", 300 | "super.init(style: .grouped)", 301 | " }"] 302 | let expectedNewLines = [ 303 | "required public init?(coder aDecoder: NSCoder) {", nil, nil] 304 | let parser = Parser(lines: lines) 305 | let newLines = parser.newLines(at: [0, 1, 2], accessChange: .singleLevel(.public)) 306 | for (index, expectedline) in expectedNewLines.enumerated() { 307 | XCTAssertEqual(newLines[index], expectedline, "Line no.: \(index) \(lines[index]) was incorrectly parsed") 308 | } 309 | } 310 | 311 | func testTypeAliasDecoration() { 312 | let lines = [ 313 | "typealias WriteToState = ((inout State) -> ()) -> ()" 314 | ] 315 | let expectedNewLines = [ 316 | "public typealias WriteToState = ((inout State) -> ()) -> ()" 317 | ] 318 | let parser = Parser(lines: lines) 319 | let newLines = parser.newLines(at: [0], accessChange: .singleLevel(.public)) 320 | for (index, expectedline) in expectedNewLines.enumerated() { 321 | XCTAssertEqual(newLines[index], expectedline, "Line no.: \(index) \(lines[index]) was incorrectly parsed") 322 | } 323 | } 324 | 325 | func testMutatingDecoration() { 326 | let lines = ["mutating func nest(_ element: FormElement) {", 327 | "strongReferences.append(contentsOf: element.strongReferences)"] 328 | let expectedNewLines = ["public mutating func nest(_ element: FormElement) {", nil] 329 | let parser = Parser(lines: lines) 330 | let newLines = parser.newLines(at: [0], accessChange: .singleLevel(.public)) 331 | for (index, expectedline) in expectedNewLines.enumerated() { 332 | XCTAssertEqual(newLines[index], expectedline, "Line no.: \(index) \(lines[index]) was incorrectly parsed") 333 | } 334 | } 335 | 336 | func testLastLineOfStructWithoutParens() { 337 | let test = 338 | """ 339 | @testable import Parser 340 | 341 | struct CloudKitIdentifiers { 342 | let container: String 343 | let placesZone : String 344 | let databaseSubscriptionID: String 345 | let placesZoneSubscriptionID : String 346 | """ 347 | let expected = 348 | """ 349 | @testable import Parser 350 | 351 | public struct CloudKitIdentifiers { 352 | public let container: String 353 | public let placesZone : String 354 | public let databaseSubscriptionID: String 355 | public let placesZoneSubscriptionID : String 356 | """ 357 | multilineTest(test: test, expected: expected) 358 | } 359 | 360 | func testEnumCases() { 361 | 362 | let test = 363 | """ 364 | enum Reps { 365 | case count(Int) 366 | case range(ClosedRange) 367 | case amrap 368 | case dropset(count: Int) 369 | case rpe(Int) 370 | case max(Int) 371 | case time(Int) 372 | case ladder([Int]) 373 | } 374 | """ 375 | let expected = 376 | """ 377 | public enum Reps { 378 | case count(Int) 379 | case range(ClosedRange) 380 | case amrap 381 | case dropset(count: Int) 382 | case rpe(Int) 383 | case max(Int) 384 | case time(Int) 385 | case ladder([Int]) 386 | } 387 | """ 388 | multilineTest(test: test, expected: expected) 389 | } 390 | 391 | 392 | func testComputedVariables() { 393 | let test = """ 394 | var oneOrMore: Parser<[A]> { 395 | // prepend the single result + remainder many result 396 | let transform: (A, [A]) -> [A] = { single, array in return [single] + array } 397 | let curried = curry(transform) 398 | let intermediate = curried <^> self // Parser<([A]) -> [A]> 399 | let final = intermediate <*> self.many // Parser<[A]> 400 | return final 401 | } 402 | """ 403 | let expected = """ 404 | public var oneOrMore: Parser<[A]> { 405 | // prepend the single result + remainder many result 406 | let transform: (A, [A]) -> [A] = { single, array in return [single] + array } 407 | let curried = curry(transform) 408 | let intermediate = curried <^> self // Parser<([A]) -> [A]> 409 | let final = intermediate <*> self.many // Parser<[A]> 410 | return final 411 | """ 412 | multilineTest(test: test, expected: expected) 413 | } 414 | 415 | func testComplexAssignmentsWithCurlyBraces() { 416 | let test = """ 417 | private let wales = countries[0].regions.first { $0.name == "Wales" }! 418 | private let england = countries[0].regions.first { $0.name == "England" }! 419 | private let english = england.languages.first { $0.name == "English" }! 420 | 421 | """ 422 | let expected = """ 423 | public let wales = countries[0].regions.first { $0.name == "Wales" }! 424 | public let england = countries[0].regions.first { $0.name == "England" }! 425 | public let english = england.languages.first { $0.name == "English" }! 426 | 427 | """ 428 | multilineTest(test: test, expected: expected) 429 | } 430 | 431 | func testComplexAssignmentListWithFancyOperators() { 432 | let test = """ 433 | let range = curry({ from, _, to in return RepStyle.range(from...to) }) <^> digits <*> hyphen <*> digits //8-12 434 | let dropset = curry({ _, _, count in return RepStyle.dropset(count: count) }) <^> string("dropset") <*> hyphen <*> digits //dropset-4 435 | let count = { RepStyle.count($0) } <^> digits <* character { $0 == "x" } //15 436 | let amrap = { _ in RepStyle.amrap } <^> string("AMRAP") 437 | let time = { RepStyle.time($0) } <^> digits <* character { $0 == "s" } //30s 438 | let rpe = { RepStyle.rpe($0) } <^> (string("rpe") *> digits) //rpe8 439 | let ladder = { RepStyle.ladder($0) } <^> (digits <* character { $0 == "," }).oneOrMore // 12,10,8,6,8,10,12 440 | let max = { RepStyle.max($0) } <^> digits <* character { $0 == "%" } //30% 441 | let repStyle = ladder <|> dropset <|> range <|> time <|> rpe <|> amrap <|> max <|> count 442 | """ 443 | let expected = """ 444 | public let range = curry({ from, _, to in return RepStyle.range(from...to) }) <^> digits <*> hyphen <*> digits //8-12 445 | public let dropset = curry({ _, _, count in return RepStyle.dropset(count: count) }) <^> string("dropset") <*> hyphen <*> digits //dropset-4 446 | public let count = { RepStyle.count($0) } <^> digits <* character { $0 == "x" } //15 447 | public let amrap = { _ in RepStyle.amrap } <^> string("AMRAP") 448 | public let time = { RepStyle.time($0) } <^> digits <* character { $0 == "s" } //30s 449 | public let rpe = { RepStyle.rpe($0) } <^> (string("rpe") *> digits) //rpe8 450 | public let ladder = { RepStyle.ladder($0) } <^> (digits <* character { $0 == "," }).oneOrMore // 12,10,8,6,8,10,12 451 | public let max = { RepStyle.max($0) } <^> digits <* character { $0 == "%" } //30% 452 | public let repStyle = ladder <|> dropset <|> range <|> time <|> rpe <|> amrap <|> max <|> count 453 | """ 454 | multilineTest(test: test, expected: expected) 455 | } 456 | 457 | func testIncompleteBraceSelection() { 458 | let test = """ 459 | struct CloudKitIdentifiers { 460 | let container: String 461 | let placesZone : String 462 | let databaseSubscriptionID: String 463 | let placesZoneSubscriptionID : String 464 | """ 465 | let expected = """ 466 | public struct CloudKitIdentifiers { 467 | public let container: String 468 | public let placesZone : String 469 | public let databaseSubscriptionID: String 470 | public let placesZoneSubscriptionID : String 471 | """ 472 | multilineTest(test: test, expected: expected) 473 | } 474 | 475 | func testVariablesDefinedWithLocalScopeInVarDontGetAccessNotation() { 476 | let test = """ 477 | var patchMark: String { 478 | let fstMark = "yo" 479 | let sndMark = "beef" 480 | return "@@@@" 481 | } 482 | """ 483 | let expected = """ 484 | public var patchMark: String { 485 | let fstMark = "yo" 486 | let sndMark = "beef" 487 | return "@@@@" 488 | } 489 | """ 490 | multilineTest(test: test, expected: expected) 491 | } 492 | 493 | func testComplexAssignmentsVarsWithCurlyBraces() { 494 | let test = """ 495 | private var wales = countries[0].regions.first { $0.name == "Wales" }! 496 | private var england = countries[0].regions.first { $0.name == "England" }! 497 | private var english = england.languages.first { $0.name == "English" }! 498 | 499 | """ 500 | let expected = """ 501 | public var wales = countries[0].regions.first { $0.name == "Wales" }! 502 | public var england = countries[0].regions.first { $0.name == "England" }! 503 | public var english = england.languages.first { $0.name == "English" }! 504 | 505 | """ 506 | multilineTest(test: test, expected: expected) 507 | } 508 | 509 | func testVariablesDefinedWithLocalScopeInLetDontGetAccessNotation() { 510 | let test = """ 511 | let patchMark: String { 512 | let fstMark = "yo" 513 | let sndMark = "beef" 514 | return "@@@@" 515 | }() 516 | """ 517 | let expected = """ 518 | public let patchMark: String { 519 | let fstMark = "yo" 520 | let sndMark = "beef" 521 | return "@@@@" 522 | }() 523 | """ 524 | multilineTest(test: test, expected: expected) 525 | } 526 | 527 | func testAccessModifierAfterStaticKeywordIsRecognized() { 528 | let test = """ 529 | static private func == (lhs: Index, rhs: Index) -> Bool { 530 | switch (lhs, rhs) { 531 | case (.array(let left), .array(let right)): 532 | return left == right 533 | case (.dictionary(let left), .dictionary(let right)): 534 | return left == right 535 | case (.null, .null): return true 536 | default: 537 | return false 538 | } 539 | } 540 | """ 541 | 542 | let expected = """ 543 | static public func == (lhs: Index, rhs: Index) -> Bool { 544 | switch (lhs, rhs) { 545 | case (.array(let left), .array(let right)): 546 | return left == right 547 | case (.dictionary(let left), .dictionary(let right)): 548 | return left == right 549 | case (.null, .null): return true 550 | default: 551 | return false 552 | } 553 | } 554 | """ 555 | multilineTest(test: test, expected: expected) 556 | } 557 | 558 | func testStaticKeywordRecognizedCorrectly() { 559 | let test = """ 560 | static var playing: [PlayerCore] { 561 | return playerCores.filter { !$0.info.isIdle } 562 | } 563 | 564 | static var playerCores: [PlayerCore] = [] 565 | static private var playerCoreCounter = 0 566 | 567 | static private func findIdlePlayerCore() -> PlayerCore? { 568 | return playerCores.first { $0.info.isIdle && !$0.info.fileLoading } 569 | } 570 | 571 | static private func createPlayerCore() -> PlayerCore { 572 | let pc = PlayerCore() 573 | playerCores.append(pc) 574 | pc.startMPV() 575 | playerCoreCounter += 1 576 | return pc 577 | } 578 | 579 | static func activeOrNewForMenuAction(isAlternative: Bool) -> PlayerCore { 580 | let useNew = Preference.bool(for: .alwaysOpenInNewWindow) != isAlternative 581 | return useNew ? newPlayerCore : active 582 | } 583 | """ 584 | let expected = """ 585 | static public var playing: [PlayerCore] { 586 | return playerCores.filter { !$0.info.isIdle } 587 | } 588 | 589 | static public var playerCores: [PlayerCore] = [] 590 | static public var playerCoreCounter = 0 591 | 592 | static public func findIdlePlayerCore() -> PlayerCore? { 593 | return playerCores.first { $0.info.isIdle && !$0.info.fileLoading } 594 | } 595 | 596 | static public func createPlayerCore() -> PlayerCore { 597 | let pc = PlayerCore() 598 | playerCores.append(pc) 599 | pc.startMPV() 600 | playerCoreCounter += 1 601 | return pc 602 | } 603 | 604 | static public func activeOrNewForMenuAction(isAlternative: Bool) -> PlayerCore { 605 | let useNew = Preference.bool(for: .alwaysOpenInNewWindow) != isAlternative 606 | return useNew ? newPlayerCore : active 607 | } 608 | """ 609 | multilineTest(test: test, expected: expected) 610 | } 611 | 612 | func testIgnoreAvailableModifier() { 613 | let test = """ 614 | /// The static null JSON 615 | @available(*, unavailable, renamed:"null") 616 | private static var nullJSON: JSON { return null } 617 | private static var null: JSON { return JSON(NSNull()) } 618 | """ 619 | let expected = """ 620 | /// The static null JSON 621 | @available(*, unavailable, renamed:"null") 622 | public static var nullJSON: JSON { return null } 623 | public static var null: JSON { return JSON(NSNull()) } 624 | """ 625 | multilineTest(test: test, expected: expected) 626 | } 627 | 628 | func testObjcModifierIgnored() { 629 | let test = """ 630 | @objc 631 | private func droppedText(_ pboard: NSPasteboard, userData:String, error: NSErrorPointer) { 632 | if let url = pboard.string(forType: .string) { 633 | openFileCalled = true 634 | PlayerCore.active.openURLString(url) 635 | } 636 | } 637 | """ 638 | let expected = """ 639 | @objc 640 | public func droppedText(_ pboard: NSPasteboard, userData:String, error: NSErrorPointer) { 641 | if let url = pboard.string(forType: .string) { 642 | openFileCalled = true 643 | PlayerCore.active.openURLString(url) 644 | } 645 | } 646 | """ 647 | multilineTest(test: test, expected: expected) 648 | } 649 | 650 | func testUnownedKeyword() { 651 | let test = """ 652 | unowned let player: PlayerCore 653 | unowned(safe) let safeplayer: PlayerCore 654 | unowned(unsafe) let unsafeplayer: PlayerCore 655 | """ 656 | let expected = """ 657 | unowned public let player: PlayerCore 658 | unowned(safe) public let safeplayer: PlayerCore 659 | unowned(unsafe) public let unsafeplayer: PlayerCore 660 | """ 661 | multilineTest(test: test, expected: expected) 662 | } 663 | 664 | func testMakeAPI() { 665 | let test = """ 666 | struct TestStruct { 667 | private let privateProperty = "Ooh la la" 668 | fileprivate let fileprivateProperty = "Not so secret" 669 | let property = "A bit hush hush" 670 | internal let property = "Exactly the same amount of hush" 671 | public let property "Open secret" 672 | } 673 | """ 674 | let expected = """ 675 | public struct TestStruct { 676 | private let privateProperty = "Ooh la la" 677 | fileprivate let fileprivateProperty = "Not so secret" 678 | public let property = "A bit hush hush" 679 | public let property = "Exactly the same amount of hush" 680 | public let property "Open secret" 681 | } 682 | """ 683 | multilineTest(test: test, expected: expected, accessChange: .makeAPI) 684 | } 685 | 686 | func testRemoveAPI() { 687 | let test = """ 688 | struct TestStruct { 689 | private let privateProperty = "Ooh la la" 690 | fileprivate let fileprivateProperty = "Not so secret" 691 | let property = "A bit hush hush" 692 | internal let property = "Exactly the same amount of hush" 693 | public let property "Open secret" 694 | } 695 | """ 696 | let expected = """ 697 | struct TestStruct { 698 | private let privateProperty = "Ooh la la" 699 | fileprivate let fileprivateProperty = "Not so secret" 700 | let property = "A bit hush hush" 701 | internal let property = "Exactly the same amount of hush" 702 | let property "Open secret" 703 | } 704 | """ 705 | multilineTest(test: test, expected: expected, accessChange: .removeAPI) 706 | } 707 | 708 | func testIncreaseAccess() { 709 | let test = """ 710 | struct TestStruct { 711 | private let privateProperty = "Ooh la la" 712 | fileprivate let fileprivateProperty = "Not so secret" 713 | let property = "A bit hush hush" 714 | internal let property = "Exactly the same amount of hush" 715 | public let property "Open secret" 716 | } 717 | """ 718 | let expected = """ 719 | public struct TestStruct { 720 | let privateProperty = "Ooh la la" 721 | let fileprivateProperty = "Not so secret" 722 | public let property = "A bit hush hush" 723 | public let property = "Exactly the same amount of hush" 724 | public let property "Open secret" 725 | } 726 | """ 727 | multilineTest(test: test, expected: expected, accessChange: .increaseAccess) 728 | } 729 | 730 | func testDecreaseAccess() { 731 | let test = """ 732 | struct TestStruct { 733 | private let privateProperty = "Ooh la la" 734 | fileprivate let fileprivateProperty = "Not so secret" 735 | let property = "A bit hush hush" 736 | internal let property = "Exactly the same amount of hush" 737 | public let property "Open secret" 738 | } 739 | """ 740 | let expected = """ 741 | private struct TestStruct { 742 | private let privateProperty = "Ooh la la" 743 | private let fileprivateProperty = "Not so secret" 744 | private let property = "A bit hush hush" 745 | private let property = "Exactly the same amount of hush" 746 | let property "Open secret" 747 | } 748 | """ 749 | multilineTest(test: test, expected: expected, accessChange: .decreaseAccess) 750 | } 751 | 752 | func testMakeAPIWithSpaces() { 753 | let test = """ 754 | class ViewController: NSViewController { 755 | 756 | struct ThisShouldHaveBeenPublic { 757 | 758 | internal var foo: String? 759 | 760 | } 761 | 762 | 763 | 764 | 765 | } 766 | """ 767 | let expected = """ 768 | public class ViewController: NSViewController { 769 | 770 | public struct ThisShouldHaveBeenPublic { 771 | 772 | public var foo: String? 773 | 774 | } 775 | 776 | 777 | 778 | 779 | } 780 | """ 781 | multilineTest(test: test, expected: expected) 782 | } 783 | 784 | func testLocalScope() { 785 | 786 | let test = """ 787 | protocol TopLevelProtocol { 788 | func functionOne() 789 | var propertyOne: String { get set } 790 | } 791 | 792 | extension TopLevelProtocol { 793 | func newFunction() { 794 | } 795 | var extensionDefinedVariable: String { 796 | let hello = "hello" 797 | return hello 798 | } 799 | } 800 | 801 | struct TopLevelStruct { 802 | class ViewController: NSViewController { 803 | let topLevel = "Top level" 804 | struct InternalStruct { 805 | let internalProperty: String = { 806 | let localScope = "Internal" 807 | return localScope 808 | }() 809 | struct NestedStruct { 810 | let nested: String = { 811 | let localScope = "Nested" 812 | return localScope 813 | }() 814 | struct DoublyNestedStruct { 815 | let double = "Double" 816 | } 817 | } 818 | } 819 | } 820 | } 821 | 822 | extension TopLevelStruct: Equatable { 823 | var extraProperty: String { 824 | let thing = "thing" 825 | return thing 826 | } 827 | } 828 | 829 | class ViewController: NSViewController { 830 | let topLevel = "Top level" 831 | struct InternalStruct { 832 | let internalProperty = "Internal" 833 | struct NestedStruct { 834 | let nested = "Nested" 835 | struct DoublyNestedStruct { 836 | let double = "Double" 837 | } 838 | } 839 | } 840 | } 841 | 842 | """ 843 | let expected = """ 844 | public protocol TopLevelProtocol { 845 | func functionOne() 846 | var propertyOne: String { get set } 847 | } 848 | 849 | public extension TopLevelProtocol { 850 | public func newFunction() { 851 | } 852 | public var extensionDefinedVariable: String { 853 | let hello = "hello" 854 | return hello 855 | } 856 | } 857 | 858 | public struct TopLevelStruct { 859 | public class ViewController: NSViewController { 860 | public let topLevel = "Top level" 861 | public struct InternalStruct { 862 | public let internalProperty: String = { 863 | let localScope = "Internal" 864 | return localScope 865 | }() 866 | public struct NestedStruct { 867 | public let nested: String = { 868 | let localScope = "Nested" 869 | return localScope 870 | }() 871 | public struct DoublyNestedStruct { 872 | public let double = "Double" 873 | } 874 | } 875 | } 876 | } 877 | } 878 | 879 | extension TopLevelStruct: Equatable { 880 | public var extraProperty: String { 881 | let thing = "thing" 882 | return thing 883 | } 884 | } 885 | 886 | public class ViewController: NSViewController { 887 | public let topLevel = "Top level" 888 | public struct InternalStruct { 889 | public let internalProperty = "Internal" 890 | public struct NestedStruct { 891 | public let nested = "Nested" 892 | public struct DoublyNestedStruct { 893 | public let double = "Double" 894 | } 895 | } 896 | } 897 | } 898 | 899 | """ 900 | multilineTest(test: test, expected: expected, accessChange: .makeAPI) 901 | } 902 | 903 | func testForLoop() { 904 | let test = """ 905 | for option in info { 906 | switch option { 907 | case .targetCache(let value): targetCache = value 908 | """ 909 | let expected = """ 910 | for option in info { 911 | switch option { 912 | case .targetCache(let value): targetCache = value 913 | """ 914 | multilineTest(test: test, expected: expected) 915 | } 916 | 917 | 918 | 919 | func testFunctionAfterDiscardableResult() { 920 | let test = """ 921 | @discardableResult 922 | func setImage( 923 | with resource: Resource?, 924 | for state: UIControl.State, 925 | placeholder: UIImage? = nil, 926 | options: KingfisherOptionsInfo? = nil, 927 | progressBlock: DownloadProgressBlock? = nil, 928 | completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? 929 | { 930 | return setImage( 931 | with: resource.map { Source.network($0) }, 932 | for: state, 933 | placeholder: placeholder, 934 | options: options, 935 | progressBlock: progressBlock, 936 | completionHandler: completionHandler) 937 | } 938 | """ 939 | let expected = """ 940 | @discardableResult 941 | public func setImage( 942 | with resource: Resource?, 943 | for state: UIControl.State, 944 | placeholder: UIImage? = nil, 945 | options: KingfisherOptionsInfo? = nil, 946 | progressBlock: DownloadProgressBlock? = nil, 947 | completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? 948 | { 949 | return setImage( 950 | with: resource.map { Source.network($0) }, 951 | for: state, 952 | placeholder: placeholder, 953 | options: options, 954 | progressBlock: progressBlock, 955 | completionHandler: completionHandler) 956 | } 957 | """ 958 | multilineTest(test: test, expected: expected) 959 | } 960 | 961 | func testConvenienceInit() { 962 | let test = """ 963 | extension NSBezierPath { 964 | convenience init(roundedRect rect: NSRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, 965 | bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat) 966 | { 967 | self.init() 968 | 969 | let maxCorner = min(rect.width, rect.height) / 2 970 | 971 | let radiusTopLeft = min(maxCorner, max(0, topLeftRadius)) 972 | let radiusTopRight = min(maxCorner, max(0, topRightRadius)) 973 | let radiusBottomLeft = min(maxCorner, max(0, bottomLeftRadius)) 974 | let radiusBottomRight = min(maxCorner, max(0, bottomRightRadius)) 975 | 976 | guard !rect.isEmpty else { 977 | return 978 | } 979 | 980 | let topLeft = NSPoint(x: rect.minX, y: rect.maxY) 981 | let topRight = NSPoint(x: rect.maxX, y: rect.maxY) 982 | let bottomRight = NSPoint(x: rect.maxX, y: rect.minY) 983 | 984 | move(to: NSPoint(x: rect.midX, y: rect.maxY)) 985 | appendArc(from: topLeft, to: rect.origin, radius: radiusTopLeft) 986 | appendArc(from: rect.origin, to: bottomRight, radius: radiusBottomLeft) 987 | appendArc(from: bottomRight, to: topRight, radius: radiusBottomRight) 988 | appendArc(from: topRight, to: topLeft, radius: radiusTopRight) 989 | close() 990 | } 991 | """ 992 | let expected = """ 993 | public extension NSBezierPath { 994 | convenience public init(roundedRect rect: NSRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, 995 | bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat) 996 | { 997 | self.init() 998 | 999 | let maxCorner = min(rect.width, rect.height) / 2 1000 | 1001 | let radiusTopLeft = min(maxCorner, max(0, topLeftRadius)) 1002 | let radiusTopRight = min(maxCorner, max(0, topRightRadius)) 1003 | let radiusBottomLeft = min(maxCorner, max(0, bottomLeftRadius)) 1004 | let radiusBottomRight = min(maxCorner, max(0, bottomRightRadius)) 1005 | 1006 | guard !rect.isEmpty else { 1007 | return 1008 | } 1009 | 1010 | let topLeft = NSPoint(x: rect.minX, y: rect.maxY) 1011 | let topRight = NSPoint(x: rect.maxX, y: rect.maxY) 1012 | let bottomRight = NSPoint(x: rect.maxX, y: rect.minY) 1013 | 1014 | move(to: NSPoint(x: rect.midX, y: rect.maxY)) 1015 | appendArc(from: topLeft, to: rect.origin, radius: radiusTopLeft) 1016 | appendArc(from: rect.origin, to: bottomRight, radius: radiusBottomLeft) 1017 | appendArc(from: bottomRight, to: topRight, radius: radiusBottomRight) 1018 | appendArc(from: topRight, to: topLeft, radius: radiusTopRight) 1019 | close() 1020 | } 1021 | """ 1022 | multilineTest(test: test, expected: expected) 1023 | } 1024 | 1025 | func testConvenienceInitRecognizedAsAccessModifiable() { 1026 | let test = """ 1027 | private extension NSImage { 1028 | // macOS does not support scale. This is just for code compatibility across platforms. 1029 | private convenience init?(data: Data, scale: CGFloat) { 1030 | self.init(data: data) 1031 | } 1032 | } 1033 | """ 1034 | let expected = """ 1035 | public extension NSImage { 1036 | // macOS does not support scale. This is just for code compatibility across platforms. 1037 | public convenience init?(data: Data, scale: CGFloat) { 1038 | self.init(data: data) 1039 | } 1040 | } 1041 | """ 1042 | multilineTest(test: test, expected: expected) 1043 | } 1044 | 1045 | func testControlFlowStructures() { 1046 | let test = """ 1047 | while things.isEmpty { 1048 | var forLocalProperty: String = "hello" 1049 | func forLocalFunc() { 1050 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1051 | } 1052 | } 1053 | for thing in things { 1054 | var forLocalProperty: String = "hello" 1055 | func forLocalFunc() { 1056 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1057 | } 1058 | } 1059 | 1060 | repeat { 1061 | var forLocalProperty: String = "hello" 1062 | func forLocalFunc() { 1063 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1064 | } 1065 | } while things.isEmpty 1066 | 1067 | while things.isEmpty { 1068 | var forLocalProperty: String = "hello" 1069 | func forLocalFunc() { 1070 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1071 | } 1072 | } 1073 | """ 1074 | let expected = """ 1075 | while things.isEmpty { 1076 | var forLocalProperty: String = "hello" 1077 | func forLocalFunc() { 1078 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1079 | } 1080 | } 1081 | for thing in things { 1082 | var forLocalProperty: String = "hello" 1083 | func forLocalFunc() { 1084 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1085 | } 1086 | } 1087 | 1088 | repeat { 1089 | var forLocalProperty: String = "hello" 1090 | func forLocalFunc() { 1091 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1092 | } 1093 | } while things.isEmpty 1094 | 1095 | while things.isEmpty { 1096 | var forLocalProperty: String = "hello" 1097 | func forLocalFunc() { 1098 | var forEvenMoreLocalVar: String = "ooh ah mrs" 1099 | } 1100 | } 1101 | """ 1102 | multilineTest(test: test, expected: expected) 1103 | } 1104 | 1105 | func testTryAndErrorHandling() { 1106 | let test = """ 1107 | try! doThis() 1108 | try? doThat() 1109 | do { 1110 | try Int.init("1234") 1111 | let localScope = "Local scope" 1112 | } catch SpecificError 1113 | return 1114 | } catch { 1115 | let localScope = "Local scope" 1116 | fatalError() 1117 | } 1118 | throw 1119 | """ 1120 | let expected = """ 1121 | try! doThis() 1122 | try? doThat() 1123 | do { 1124 | try Int.init("1234") 1125 | let localScope = "Local scope" 1126 | } catch SpecificError 1127 | return 1128 | } catch { 1129 | let localScope = "Local scope" 1130 | fatalError() 1131 | } 1132 | throw 1133 | """ 1134 | multilineTest(test: test, expected: expected) 1135 | } 1136 | 1137 | func testControlFlowKeywords() { 1138 | let test = """ 1139 | break 1140 | return 1141 | continue 1142 | fallthrough 1143 | """ 1144 | let expected = """ 1145 | break 1146 | return 1147 | continue 1148 | fallthrough 1149 | """ 1150 | multilineTest(test: test, expected: expected) 1151 | } 1152 | 1153 | func testDeferBlock() { 1154 | let test = """ 1155 | defer { 1156 | let localProperty = "local string" 1157 | } 1158 | """ 1159 | let expected = """ 1160 | defer { 1161 | let localProperty = "local string" 1162 | } 1163 | """ 1164 | multilineTest(test: test, expected: expected) 1165 | } 1166 | 1167 | func testCompilerControlStatements() { 1168 | let test = """ 1169 | #if something 1170 | let localPropertyA = "Local" 1171 | 1172 | #elseif somethingelse 1173 | let localPropertyB = "Local" 1174 | 1175 | #else somethingelseentirely 1176 | let localPropertyC = "Local" 1177 | 1178 | #endif 1179 | 1180 | struct Thingie { 1181 | 1182 | init() { 1183 | if #available(*, *) { 1184 | let localPropertyD = "Local" 1185 | } else { 1186 | let localPropertyE = "Local" 1187 | } 1188 | } 1189 | 1190 | } 1191 | 1192 | """ 1193 | let expected = """ 1194 | #if something 1195 | public let localPropertyA = "Local" 1196 | 1197 | #elseif somethingelse 1198 | public let localPropertyB = "Local" 1199 | 1200 | #else somethingelseentirely 1201 | public let localPropertyC = "Local" 1202 | 1203 | #endif 1204 | 1205 | public struct Thingie { 1206 | 1207 | public init() { 1208 | if #available(*, *) { 1209 | let localPropertyD = "Local" 1210 | } else { 1211 | let localPropertyE = "Local" 1212 | } 1213 | } 1214 | 1215 | } 1216 | """ 1217 | multilineTest(test: test, expected: expected) 1218 | } 1219 | 1220 | func testSubscriptDefinition() { 1221 | let test = """ 1222 | subscript (Int) -> String { 1223 | get { 1224 | let localPropertyA = "Local" 1225 | } 1226 | set { 1227 | let localPropertyB = "Local" 1228 | } 1229 | } 1230 | """ 1231 | let expected = """ 1232 | public subscript (Int) -> String { 1233 | get { 1234 | let localPropertyA = "Local" 1235 | } 1236 | set { 1237 | let localPropertyB = "Local" 1238 | } 1239 | } 1240 | """ 1241 | multilineTest(test: test, expected: expected) 1242 | } 1243 | 1244 | func testOperatorDefinition() { 1245 | let test = """ 1246 | prefix operator NotAnEmojiPlease 1247 | postfix operator NotAnEmojiPlease 1248 | infix operator NotAnEmojiPlease: DefaultPrecendence 1249 | """ 1250 | let expected = """ 1251 | prefix operator NotAnEmojiPlease 1252 | postfix operator NotAnEmojiPlease 1253 | infix operator NotAnEmojiPlease: DefaultPrecendence 1254 | """ 1255 | multilineTest(test: test, expected: expected) 1256 | } 1257 | 1258 | func testPrefixForOperatorFunctionDefinitions() { 1259 | let test = """ 1260 | prefix operator ^ 1261 | prefix func ^ (_ kp: KeyPath) -> (Root) -> Value { 1262 | return get(kp) 1263 | } 1264 | """ 1265 | 1266 | let expected = """ 1267 | prefix operator ^ 1268 | public prefix func ^ (_ kp: KeyPath) -> (Root) -> Value { 1269 | return get(kp) 1270 | } 1271 | """ 1272 | multilineTest(test: test, expected: expected) 1273 | } 1274 | 1275 | func testPrecedenceGroupDefinition() { 1276 | let test = """ 1277 | precedencegroup SpecialPrecedence { 1278 | higherThan: TernaryPrecedence 1279 | lowerThan: DefaultPrecedence 1280 | associativity: right 1281 | assignment: true 1282 | } 1283 | """ 1284 | let expected = """ 1285 | precedencegroup SpecialPrecedence { 1286 | higherThan: TernaryPrecedence 1287 | lowerThan: DefaultPrecedence 1288 | associativity: right 1289 | assignment: true 1290 | } 1291 | """ 1292 | multilineTest(test: test, expected: expected) 1293 | } 1294 | 1295 | func testMoreModifiers() { 1296 | let test = """ 1297 | class ViewController: NSViewController { 1298 | @objc dynamic let things: [String] = [] 1299 | final class MyFinalClass { 1300 | let internalProperty: String = "Hello world!" 1301 | } 1302 | lazy var internalLazyThing: String = "Hello" 1303 | weak var internalWeakLazyThing: NSNumber? = NSNumber(value: 50) 1304 | override func viewDidLoad() { 1305 | super.viewDidLoad() 1306 | } 1307 | } 1308 | """ 1309 | let expected = """ 1310 | public class ViewController: NSViewController { 1311 | @objc public dynamic let things: [String] = [] 1312 | public final class MyFinalClass { 1313 | public let internalProperty: String = "Hello world!" 1314 | } 1315 | public lazy var internalLazyThing: String = "Hello" 1316 | public weak var internalWeakLazyThing: NSNumber? = NSNumber(value: 50) 1317 | public override func viewDidLoad() { 1318 | super.viewDidLoad() 1319 | } 1320 | } 1321 | """ 1322 | multilineTest(test: test, expected: expected) 1323 | } 1324 | 1325 | func testOpenToPublic() { 1326 | let test = """ 1327 | open class ViewController: NSViewController { 1328 | 1329 | @objc open dynamic let things: [String] = [] 1330 | 1331 | open class MyFinalClass { 1332 | let internalProperty: String = "Hello world!" 1333 | } 1334 | 1335 | open lazy var internalLazyThing: String = "Hello" 1336 | 1337 | open weak var internalWeakLazyThing: NSNumber? = NSNumber(value: 50) 1338 | 1339 | open override func viewDidLoad() { 1340 | super.viewDidLoad() 1341 | } 1342 | } 1343 | """ 1344 | let expected = """ 1345 | public class ViewController: NSViewController { 1346 | 1347 | @objc public dynamic let things: [String] = [] 1348 | 1349 | public class MyFinalClass { 1350 | public let internalProperty: String = "Hello world!" 1351 | } 1352 | 1353 | public lazy var internalLazyThing: String = "Hello" 1354 | 1355 | public weak var internalWeakLazyThing: NSNumber? = NSNumber(value: 50) 1356 | 1357 | public override func viewDidLoad() { 1358 | super.viewDidLoad() 1359 | } 1360 | } 1361 | """ 1362 | multilineTest(test: test, expected: expected) 1363 | } 1364 | 1365 | func testMakeAPIInEntityWithLowerThanInternalAccessShouldFail() { 1366 | let test = """ 1367 | private extension Human { 1368 | func callForServiceA() { 1369 | // tbd 1370 | } 1371 | } 1372 | fileprivate extension Human { 1373 | func callForServiceB() { 1374 | // tbd 1375 | } 1376 | } 1377 | extension Human { 1378 | func callForServiceC() { 1379 | // tbd 1380 | } 1381 | } 1382 | public extension Human { 1383 | func callForServiceD() { 1384 | // tbd 1385 | } 1386 | } 1387 | """ 1388 | let expected = """ 1389 | private extension Human { 1390 | func callForServiceA() { 1391 | // tbd 1392 | } 1393 | } 1394 | fileprivate extension Human { 1395 | func callForServiceB() { 1396 | // tbd 1397 | } 1398 | } 1399 | public extension Human { 1400 | public func callForServiceC() { 1401 | // tbd 1402 | } 1403 | } 1404 | public extension Human { 1405 | public func callForServiceD() { 1406 | // tbd 1407 | } 1408 | } 1409 | """ 1410 | multilineTest(test: test, expected: expected, accessChange: .makeAPI) 1411 | } 1412 | 1413 | func testRemoveAPIInEntityWithLowerThanInternalAccessShouldFail() { 1414 | let test = """ 1415 | private extension Human { 1416 | func callForServiceA() { 1417 | // tbd 1418 | } 1419 | } 1420 | fileprivate extension Human { 1421 | func callForServiceB() { 1422 | // tbd 1423 | } 1424 | } 1425 | extension Human { 1426 | public func callForServiceC() { 1427 | // tbd 1428 | } 1429 | } 1430 | public extension Human { 1431 | func callForServiceD() { 1432 | // tbd 1433 | } 1434 | } 1435 | public extension Human { 1436 | public func callForServiceD() { 1437 | // tbd 1438 | } 1439 | } 1440 | """ 1441 | let expected = """ 1442 | private extension Human { 1443 | func callForServiceA() { 1444 | // tbd 1445 | } 1446 | } 1447 | fileprivate extension Human { 1448 | func callForServiceB() { 1449 | // tbd 1450 | } 1451 | } 1452 | extension Human { 1453 | func callForServiceC() { 1454 | // tbd 1455 | } 1456 | } 1457 | extension Human { 1458 | func callForServiceD() { 1459 | // tbd 1460 | } 1461 | } 1462 | extension Human { 1463 | func callForServiceD() { 1464 | // tbd 1465 | } 1466 | } 1467 | """ 1468 | multilineTest(test: test, expected: expected, accessChange: .removeAPI) 1469 | } 1470 | 1471 | func testIncrementInEntityWithLowerThanInternalImplicitAccessShouldFail() { 1472 | let test = """ 1473 | private extension Human { 1474 | func implicitPrivate() { 1475 | // tbd 1476 | } 1477 | } 1478 | fileprivate extension Human { 1479 | func implicitFilePrivate() { 1480 | // tbd 1481 | } 1482 | } 1483 | extension Human { 1484 | func callForServiceC() { 1485 | // tbd 1486 | } 1487 | } 1488 | public extension Human { 1489 | func implicitPublic() { 1490 | // tbd 1491 | } 1492 | } 1493 | """ 1494 | let expected = """ 1495 | extension Human { 1496 | func implicitPrivate() { 1497 | // tbd 1498 | } 1499 | } 1500 | extension Human { 1501 | func implicitFilePrivate() { 1502 | // tbd 1503 | } 1504 | } 1505 | public extension Human { 1506 | public func callForServiceC() { 1507 | // tbd 1508 | } 1509 | } 1510 | public extension Human { 1511 | public func implicitPublic() { 1512 | // tbd 1513 | } 1514 | } 1515 | """ 1516 | multilineTest(test: test, expected: expected, accessChange: .increaseAccess) 1517 | } 1518 | 1519 | 1520 | func testSubscriptSetterIncrement() { 1521 | 1522 | // Increments underlying property; leaves setter alone 1523 | 1524 | let test = """ 1525 | private(set) subscript (Int) -> String { 1526 | get { 1527 | let localPropertyA = "Local" 1528 | } 1529 | set { 1530 | let localPropertyB = "Local" 1531 | } 1532 | } 1533 | """ 1534 | let expected = """ 1535 | private(set) public subscript (Int) -> String { 1536 | get { 1537 | let localPropertyA = "Local" 1538 | } 1539 | set { 1540 | let localPropertyB = "Local" 1541 | } 1542 | } 1543 | """ 1544 | multilineTest(test: test, expected: expected, accessChange: .increaseAccess) 1545 | } 1546 | 1547 | // Decrements underlying property 1548 | // coalesces with setter if appropriate 1549 | // otherwise leaves as is: 1550 | 1551 | func testSubscriptSetterDecrement() { 1552 | let test = """ 1553 | internal(set) public subscript (Int) -> String { 1554 | get { 1555 | let localPropertyA = "Local" 1556 | } 1557 | set { 1558 | let localPropertyB = "Local" 1559 | } 1560 | } 1561 | """ 1562 | let expected = """ 1563 | subscript (Int) -> String { 1564 | get { 1565 | let localPropertyA = "Local" 1566 | } 1567 | set { 1568 | let localPropertyB = "Local" 1569 | } 1570 | } 1571 | """ 1572 | multilineTest(test: test, expected: expected, accessChange: .decreaseAccess) 1573 | } 1574 | 1575 | func testSubscriptSetterMakeAPI() { 1576 | let test = """ 1577 | internal(set) subscript (Int) -> String { 1578 | get { 1579 | let localPropertyA = "Local" 1580 | } 1581 | set { 1582 | let localPropertyB = "Local" 1583 | } 1584 | } 1585 | private(set) internal subscript (Int) -> String { 1586 | get { 1587 | let localPropertyA = "Local" 1588 | } 1589 | set { 1590 | let localPropertyB = "Local" 1591 | } 1592 | } 1593 | """ 1594 | let expected = """ 1595 | internal(set) public subscript (Int) -> String { 1596 | get { 1597 | let localPropertyA = "Local" 1598 | } 1599 | set { 1600 | let localPropertyB = "Local" 1601 | } 1602 | } 1603 | private(set) public subscript (Int) -> String { 1604 | get { 1605 | let localPropertyA = "Local" 1606 | } 1607 | set { 1608 | let localPropertyB = "Local" 1609 | } 1610 | } 1611 | """ 1612 | multilineTest(test: test, expected: expected, accessChange: .makeAPI) 1613 | } 1614 | 1615 | func testSubscriptSetterRemoveAPI() { 1616 | let test = """ 1617 | internal(set) public subscript (Int) -> String { 1618 | get { 1619 | let localPropertyA = "Local" 1620 | } 1621 | set { 1622 | let localPropertyB = "Local" 1623 | } 1624 | } 1625 | """ 1626 | let expected = """ 1627 | subscript (Int) -> String { 1628 | get { 1629 | let localPropertyA = "Local" 1630 | } 1631 | set { 1632 | let localPropertyB = "Local" 1633 | } 1634 | } 1635 | """ 1636 | multilineTest(test: test, expected: expected, accessChange: .removeAPI) 1637 | } 1638 | 1639 | func testSubscriptSetterSetAllToPublic() { 1640 | let test = """ 1641 | private(set) internal subscript (Int) -> String { 1642 | get { 1643 | let localPropertyA = "Local" 1644 | } 1645 | set { 1646 | let localPropertyB = "Local" 1647 | } 1648 | } 1649 | """ 1650 | let expected = """ 1651 | public subscript (Int) -> String { 1652 | get { 1653 | let localPropertyA = "Local" 1654 | } 1655 | set { 1656 | let localPropertyB = "Local" 1657 | } 1658 | } 1659 | """ 1660 | multilineTest(test: test, expected: expected, accessChange: .singleLevel(.public)) 1661 | } 1662 | 1663 | 1664 | // Incrementing access : 1665 | 1666 | // increment the underlying property 1667 | // leave the setter alone 1668 | 1669 | 1670 | func testVarSetterIncrement() { 1671 | let test = """ 1672 | private(set) var example: String { 1673 | get { 1674 | return "An example" 1675 | } 1676 | set { 1677 | example = newValue 1678 | } 1679 | 1680 | } 1681 | private(set) fileprivate var exampleA: String = "A" 1682 | private(set) internal var exampleB: String = "B" 1683 | private(set) var exampleC: String = "C" 1684 | private(set) public var exampleD: String = "D" 1685 | 1686 | fileprivate(set) var exampleE: String = "E" 1687 | fileprivate(set) internal var exampleF: String = "F" 1688 | fileprivate(set) public var exampleG: String = "G" 1689 | 1690 | internal(set) public var exampleH: String = "H" 1691 | """ 1692 | let expected = """ 1693 | private(set) public var example: String { 1694 | get { 1695 | return "An example" 1696 | } 1697 | set { 1698 | example = newValue 1699 | } 1700 | 1701 | } 1702 | private(set) var exampleA: String = "A" 1703 | private(set) public var exampleB: String = "B" 1704 | private(set) public var exampleC: String = "C" 1705 | private(set) public var exampleD: String = "D" 1706 | 1707 | fileprivate(set) public var exampleE: String = "E" 1708 | fileprivate(set) public var exampleF: String = "F" 1709 | fileprivate(set) public var exampleG: String = "G" 1710 | 1711 | internal(set) public var exampleH: String = "H" 1712 | """ 1713 | multilineTest(test: test, expected: expected, accessChange: .increaseAccess) 1714 | } 1715 | 1716 | // Decrementing access : 1717 | 1718 | // Decrease the underlying property 1719 | // Coalesce the setter if the setter and property end up the same 1720 | // Otherwise leave as is 1721 | 1722 | func testVarSetterDecrement() { 1723 | let test = """ 1724 | private(set) var example: String { 1725 | get { 1726 | return "An example" 1727 | } 1728 | set { 1729 | example = newValue 1730 | } 1731 | 1732 | } 1733 | private(set) fileprivate var exampleA: String = "A" 1734 | private(set) internal var exampleB: String = "B" 1735 | private(set) var exampleC: String = "C" 1736 | private(set) public var exampleD: String = "D" 1737 | 1738 | fileprivate(set) var exampleE: String = "E" 1739 | fileprivate(set) internal var exampleF: String = "F" 1740 | fileprivate(set) public var exampleG: String = "G" 1741 | 1742 | internal(set) public var exampleH: String = "H" 1743 | """ 1744 | let expected = """ 1745 | private var example: String { 1746 | get { 1747 | return "An example" 1748 | } 1749 | set { 1750 | example = newValue 1751 | } 1752 | 1753 | } 1754 | private var exampleA: String = "A" 1755 | private var exampleB: String = "B" 1756 | private var exampleC: String = "C" 1757 | private(set) var exampleD: String = "D" 1758 | 1759 | private var exampleE: String = "E" 1760 | private var exampleF: String = "F" 1761 | fileprivate(set) var exampleG: String = "G" 1762 | 1763 | var exampleH: String = "H" 1764 | """ 1765 | multilineTest(test: test, expected: expected, accessChange: .decreaseAccess) 1766 | } 1767 | 1768 | func testVarSetterMakeAPI() { 1769 | let test = """ 1770 | private(set) var example: String { 1771 | return "An example" 1772 | } 1773 | private(set) fileprivate var example: String = "A" 1774 | private(set) internal var example: String = "B" 1775 | private(set) var example: String = "C" 1776 | private(set) public var example: String = "D" 1777 | 1778 | fileprivate(set) var example: String = "E" 1779 | fileprivate(set) internal var example: String = "F" 1780 | fileprivate(set) public var example: String = "G" 1781 | 1782 | internal(set) public var example: String = "A" 1783 | """ 1784 | let expected = """ 1785 | private(set) public var example: String { 1786 | return "An example" 1787 | } 1788 | private(set) fileprivate var example: String = "A" 1789 | private(set) public var example: String = "B" 1790 | private(set) public var example: String = "C" 1791 | private(set) public var example: String = "D" 1792 | 1793 | fileprivate(set) public var example: String = "E" 1794 | fileprivate(set) public var example: String = "F" 1795 | fileprivate(set) public var example: String = "G" 1796 | 1797 | internal(set) public var example: String = "A" 1798 | """ 1799 | multilineTest(test: test, expected: expected, accessChange: .makeAPI) 1800 | } 1801 | 1802 | func testVarSetterRemoveAPI() { 1803 | let test = """ 1804 | private(set) var example: String { 1805 | return "An example" 1806 | } 1807 | private(set) fileprivate var example: String = "A" 1808 | private(set) internal var example: String = "B" 1809 | private(set) var example: String = "C" 1810 | private(set) public var example: String = "D" 1811 | 1812 | fileprivate(set) var example: String = "E" 1813 | fileprivate(set) internal var example: String = "F" 1814 | fileprivate(set) public var example: String = "G" 1815 | 1816 | internal(set) public var example: String = "A" 1817 | """ 1818 | let expected = """ 1819 | private(set) var example: String { 1820 | return "An example" 1821 | } 1822 | private(set) fileprivate var example: String = "A" 1823 | private(set) internal var example: String = "B" 1824 | private(set) var example: String = "C" 1825 | private(set) var example: String = "D" 1826 | 1827 | fileprivate(set) var example: String = "E" 1828 | fileprivate(set) internal var example: String = "F" 1829 | fileprivate(set) var example: String = "G" 1830 | 1831 | var example: String = "A" 1832 | """ 1833 | multilineTest(test: test, expected: expected, accessChange: .removeAPI) 1834 | } 1835 | 1836 | func testVarSetterAllPrivate() { 1837 | let test = """ 1838 | private(set) var example: String { 1839 | return "An example" 1840 | } 1841 | private(set) fileprivate var example: String = "A" 1842 | private(set) internal var example: String = "B" 1843 | private(set) var example: String = "C" 1844 | private(set) public var example: String = "D" 1845 | 1846 | fileprivate(set) var example: String = "E" 1847 | fileprivate(set) internal var example: String = "F" 1848 | fileprivate(set) public var example: String = "G" 1849 | 1850 | internal(set) public var example: String = "A" 1851 | """ 1852 | let expected = """ 1853 | private var example: String { 1854 | return "An example" 1855 | } 1856 | private var example: String = "A" 1857 | private var example: String = "B" 1858 | private var example: String = "C" 1859 | private var example: String = "D" 1860 | 1861 | private var example: String = "E" 1862 | private var example: String = "F" 1863 | private var example: String = "G" 1864 | 1865 | private var example: String = "A" 1866 | """ 1867 | multilineTest(test: test, expected: expected, accessChange: .singleLevel(.private)) 1868 | } 1869 | 1870 | func testStaticVariableInheritanceInPublicExtension() { 1871 | let test = """ 1872 | // Base type is public 1873 | public struct MyStruct {} 1874 | 1875 | // Here, the extension is declared public, so each top level member 1876 | // "inherits" that access level. 1877 | public extension MyStruct { 1878 | // This is public even if it is not annotated 1879 | static var firstValue: String { return "public" } 1880 | 1881 | // This is also public but the compiler will warn. 1882 | public static var secondValue: String { return "public but warned" } 1883 | 1884 | // This class is also public via "inheritance" 1885 | class PublicSubclass { 1886 | // However, its members must be annotated. This is public 1887 | public static let publicValue = "public" 1888 | // This defaults to internal 1889 | static let internalValue = "internal" 1890 | } 1891 | } 1892 | """ 1893 | let expected = """ 1894 | // Base type is public 1895 | struct MyStruct {} 1896 | 1897 | // Here, the extension is declared public, so each top level member 1898 | // "inherits" that access level. 1899 | extension MyStruct { 1900 | // This is public even if it is not annotated 1901 | static var firstValue: String { return "public" } 1902 | 1903 | // This is also public but the compiler will warn. 1904 | static var secondValue: String { return "public but warned" } 1905 | 1906 | // This class is also public via "inheritance" 1907 | class PublicSubclass { 1908 | // However, its members must be annotated. This is public 1909 | static let publicValue = "public" 1910 | // This defaults to internal 1911 | static let internalValue = "internal" 1912 | } 1913 | } 1914 | """ 1915 | multilineTest(test: test, expected: expected, accessChange: .decreaseAccess) 1916 | } 1917 | 1918 | func testPublicExtensionInheritance() { 1919 | 1920 | // MAKE LINES 2 && 5 PUBLIC 1921 | 1922 | let test = 1923 | """ 1924 | public struct PublicStruct {} 1925 | public extension PublicStruct { 1926 | static var first: String { return "Implicit public" } 1927 | public static var second: String { return "Explicit public" } 1928 | class PublicSubclass { 1929 | static var subfirst: String { return "Internal" } 1930 | public static var subsecond: String { return "Public" } 1931 | } 1932 | } 1933 | """ 1934 | 1935 | let expectedPublic = 1936 | """ 1937 | public struct PublicStruct {} 1938 | public extension PublicStruct { 1939 | static var first: String { return "Implicit public" } 1940 | public static var second: String { return "Explicit public" } 1941 | class PublicSubclass { 1942 | public static var subfirst: String { return "Internal" } 1943 | public static var subsecond: String { return "Public" } 1944 | } 1945 | } 1946 | """ 1947 | multilineTest(test: test, expected: expectedPublic, linesToChange: [2, 5], accessChange: .singleLevel(.public)) 1948 | } 1949 | /* 1950 | MAKE INTERNAL 1951 | public struct PublicStruct {} 1952 | public extension PublicStruct { 1953 | internal static var first: String { return "Implicit public" } 1954 | public static var second: String { return "Explicit public" } 1955 | class PublicSubclass { 1956 | static var subfirst: String { return "Internal" } 1957 | public static var subsecond: String { return "Public" } 1958 | } 1959 | } 1960 | 1961 | MAKE API 1962 | public struct PublicStruct {} 1963 | public extension PublicStruct { 1964 | static var first: String { return "Implicit public" } 1965 | public static var second: String { return "Explicit public" } 1966 | class PublicSubclass { 1967 | public static var subfirst: String { return "Internal" } 1968 | public static var subsecond: String { return "Public" } 1969 | } 1970 | } 1971 | 1972 | REMOVE API 1973 | public struct PublicStruct {} 1974 | public extension PublicStruct { 1975 | internal static var first: String { return "Implicit public" } 1976 | public static var second: String { return "Explicit public" } 1977 | class PublicSubclass { 1978 | static var subfirst: String { return "Internal" } 1979 | public static var subsecond: String { return "Public" } 1980 | } 1981 | } 1982 | */ 1983 | 1984 | 1985 | 1986 | func testChangeOnlySelectedLines() { 1987 | let test = """ 1988 | open class ViewController: NSViewController { 1989 | @objc open dynamic let things: [String] = [] 1990 | open class MyFinalClass { 1991 | let internalProperty: String = "Hello world!" 1992 | } 1993 | open lazy var internalLazyThing: String = "Hello" 1994 | open weak var internalWeakLazyThing: NSNumber? = NSNumber(value: 50) 1995 | open override func viewDidLoad() { 1996 | super.viewDidLoad() 1997 | } 1998 | } 1999 | """ 2000 | let expected = """ 2001 | public class ViewController: NSViewController { 2002 | @objc open dynamic let things: [String] = [] 2003 | public class MyFinalClass { 2004 | let internalProperty: String = "Hello world!" 2005 | } 2006 | open lazy var internalLazyThing: String = "Hello" 2007 | public weak var internalWeakLazyThing: NSNumber? = NSNumber(value: 50) 2008 | open override func viewDidLoad() { 2009 | super.viewDidLoad() 2010 | } 2011 | } 2012 | """ 2013 | multilineTest(test: test, expected: expected, linesToChange: [0,2,6]) 2014 | } 2015 | 2016 | 2017 | 2018 | 2019 | func testSomething() { 2020 | let test = """ 2021 | 2022 | """ 2023 | let expected = """ 2024 | 2025 | """ 2026 | multilineTest(test: test, expected: expected) 2027 | } 2028 | 2029 | 2030 | func multilineTest(test: String, expected: String, linesToChange: [Int]? = nil, accessChange: AccessChange = .singleLevel(.public), file: StaticString = #file, line: UInt = #line) { 2031 | 2032 | let lines = test.components(separatedBy: .newlines) 2033 | let changing = linesToChange ?? Array(0..