├── .gitignore ├── COWRewriter.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── COWRewriter.xcscheme ├── COWRewriter ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024.png │ │ ├── Icon-129.png │ │ ├── Icon-16.png │ │ ├── Icon-256.png │ │ ├── Icon-257.png │ │ ├── Icon-32.png │ │ ├── Icon-33.png │ │ ├── Icon-512.png │ │ ├── Icon-513.png │ │ └── Icon-64.png │ └── Contents.json ├── COWRewriter.entitlements ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Printer │ ├── Printer.swift │ └── PrinterConfigs.swift ├── Refactorer │ ├── COWRewriter.swift │ ├── MakeCowStruct.swift │ ├── MakeStorageClass.swift │ ├── Refactorer.swift │ └── Sema.swift ├── Session │ ├── RefactorCandidate.swift │ ├── RefactorRequestConfig.swift │ ├── Session.swift │ └── SessionManager.swift ├── UI │ ├── BottomToolbar.swift │ ├── ContentView.swift │ ├── FileDropView.swift │ ├── FilePicker.swift │ ├── ImportErrorMessageView.swift │ ├── RefactorRequestsConfigView.swift │ ├── RefactorView.swift │ └── TheApp.swift └── Utilities │ ├── Binding+COWRewriterAdditions.swift │ ├── ConditionalKeyboardShorcut.swift │ ├── RuntimeErrorHandling.swift │ └── Stdlib+COWRewriterAdditions.swift ├── COWRewriterTests ├── Reafactorer │ ├── COWRewriterTestsBase.swift │ ├── COWRewriterWontRewriteTests.swift │ └── COWRewritertRewriteTests.swift ├── Sema │ ├── SemaRefactorableDeclRecognitionTests.swift │ ├── SemaTestsBase.swift │ └── SemaTypeInferTests.swift └── XCTestCase+COWRewriterTestsAdditions.swift ├── COWRewriterUITests ├── COWRewriterUITests.swift └── COWRewriterUITestsLaunchTests.swift ├── LICENSE ├── Promotion ├── Dashboard-Show.png ├── Step-1.png ├── Step-2.png └── Step-3.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/swift,macos 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,macos 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | ### Swift ### 38 | # Xcode 39 | # 40 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 41 | 42 | ## User settings 43 | xcuserdata/ 44 | 45 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 46 | *.xcscmblueprint 47 | *.xccheckout 48 | 49 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 50 | build/ 51 | DerivedData/ 52 | *.moved-aside 53 | *.pbxuser 54 | !default.pbxuser 55 | *.mode1v3 56 | !default.mode1v3 57 | *.mode2v3 58 | !default.mode2v3 59 | *.perspectivev3 60 | !default.perspectivev3 61 | 62 | ## Obj-C/Swift specific 63 | *.hmap 64 | 65 | ## App packaging 66 | *.ipa 67 | *.dSYM.zip 68 | *.dSYM 69 | 70 | ## Playgrounds 71 | timeline.xctimeline 72 | playground.xcworkspace 73 | 74 | # Swift Package Manager 75 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 76 | # Packages/ 77 | # Package.pins 78 | # Package.resolved 79 | # *.xcodeproj 80 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 81 | # hence it is not needed unless you have added a package configuration file to your project 82 | # .swiftpm 83 | 84 | .build/ 85 | 86 | # CocoaPods 87 | # We recommend against adding the Pods directory to your .gitignore. However 88 | # you should judge for yourself, the pros and cons are mentioned at: 89 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 90 | # Pods/ 91 | # Add this line if you want to avoid checking in source code from the Xcode workspace 92 | # *.xcworkspace 93 | 94 | # Carthage 95 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 96 | # Carthage/Checkouts 97 | 98 | Carthage/Build/ 99 | 100 | # Accio dependency management 101 | Dependencies/ 102 | .accio/ 103 | 104 | # fastlane 105 | # It is recommended to not store the screenshots in the git repo. 106 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 107 | # For more information about the recommended setup visit: 108 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 109 | 110 | fastlane/report.xml 111 | fastlane/Preview.html 112 | fastlane/screenshots/**/*.png 113 | fastlane/test_output 114 | 115 | # Code Injection 116 | # After new code Injection tools there's a generated folder /iOSInjectionProject 117 | # https://github.com/johnno1962/injectionforxcode 118 | 119 | iOSInjectionProject/ 120 | 121 | # End of https://www.toptal.com/developers/gitignore/api/swift,macos 122 | n -------------------------------------------------------------------------------- /COWRewriter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9C0809C92838BA2F00ACDCE9 /* TheApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0809C82838BA2F00ACDCE9 /* TheApp.swift */; }; 11 | 9C0809CB2838BA2F00ACDCE9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0809CA2838BA2F00ACDCE9 /* ContentView.swift */; }; 12 | 9C0809CD2838BA3000ACDCE9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9C0809CC2838BA3000ACDCE9 /* Assets.xcassets */; }; 13 | 9C0809D02838BA3000ACDCE9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9C0809CF2838BA3000ACDCE9 /* Preview Assets.xcassets */; }; 14 | 9C0809E52838BA3000ACDCE9 /* COWRewriterUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0809E42838BA3000ACDCE9 /* COWRewriterUITests.swift */; }; 15 | 9C0809E72838BA3000ACDCE9 /* COWRewriterUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0809E62838BA3000ACDCE9 /* COWRewriterUITestsLaunchTests.swift */; }; 16 | 9C0809F72838C53200ACDCE9 /* SwiftSyntax in Frameworks */ = {isa = PBXBuildFile; productRef = 9C0809F62838C53200ACDCE9 /* SwiftSyntax */; }; 17 | 9C0809F92838C53200ACDCE9 /* SwiftSyntaxBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 9C0809F82838C53200ACDCE9 /* SwiftSyntaxBuilder */; }; 18 | 9C0809FB2838C53200ACDCE9 /* SwiftSyntaxParser in Frameworks */ = {isa = PBXBuildFile; productRef = 9C0809FA2838C53200ACDCE9 /* SwiftSyntaxParser */; }; 19 | 9C0809FF2838E6C400ACDCE9 /* FilePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0809FE2838E6C400ACDCE9 /* FilePicker.swift */; }; 20 | 9C080A012838E70100ACDCE9 /* RefactorRequestsConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C080A002838E70100ACDCE9 /* RefactorRequestsConfigView.swift */; }; 21 | 9C080A032838FB5500ACDCE9 /* BottomToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C080A022838FB5500ACDCE9 /* BottomToolbar.swift */; }; 22 | 9C080A0E283965E500ACDCE9 /* COWRewriterTestsBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C080A0D283965E500ACDCE9 /* COWRewriterTestsBase.swift */; }; 23 | 9C080A102839EF7500ACDCE9 /* XCTestCase+COWRewriterTestsAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C080A0F2839EF7500ACDCE9 /* XCTestCase+COWRewriterTestsAdditions.swift */; }; 24 | 9C080A122839F0CE00ACDCE9 /* COWRewriterWontRewriteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C080A112839F0CE00ACDCE9 /* COWRewriterWontRewriteTests.swift */; }; 25 | 9C080A142839F0D400ACDCE9 /* COWRewritertRewriteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C080A132839F0D400ACDCE9 /* COWRewritertRewriteTests.swift */; }; 26 | 9C2C13DB283E743D00978CD9 /* Refactorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2C13DA283E743D00978CD9 /* Refactorer.swift */; }; 27 | 9C46396D284685DF0020FCEF /* RuntimeErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C46396C284685DF0020FCEF /* RuntimeErrorHandling.swift */; }; 28 | 9C768F9D284B2EC300FE835D /* Printer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768F9C284B2EC300FE835D /* Printer.swift */; }; 29 | 9C768F9F284B47C000FE835D /* Stdlib+COWRewriterAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768F9E284B47C000FE835D /* Stdlib+COWRewriterAdditions.swift */; }; 30 | 9C768FA1284B838A00FE835D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FA0284B838A00FE835D /* SessionManager.swift */; }; 31 | 9C768FA3284B83B800FE835D /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FA2284B83B800FE835D /* Session.swift */; }; 32 | 9C768FA5284B847200FE835D /* RefactorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FA4284B847200FE835D /* RefactorView.swift */; }; 33 | 9C768FA7284B89CF00FE835D /* FileDropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FA6284B89CF00FE835D /* FileDropView.swift */; }; 34 | 9C768FA9284BA13C00FE835D /* ImportErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FA8284BA13C00FE835D /* ImportErrorMessageView.swift */; }; 35 | 9C768FAB284BB9F500FE835D /* ConditionalKeyboardShorcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FAA284BB9F500FE835D /* ConditionalKeyboardShorcut.swift */; }; 36 | 9C768FB0284BD2EC00FE835D /* RefactorCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FAF284BD2EC00FE835D /* RefactorCandidate.swift */; }; 37 | 9C768FB2284BD30200FE835D /* RefactorRequestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FB1284BD30200FE835D /* RefactorRequestConfig.swift */; }; 38 | 9C768FB5284E267400FE835D /* MakeStorageClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FB4284E267400FE835D /* MakeStorageClass.swift */; }; 39 | 9C768FB7284E270700FE835D /* MakeCowStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C768FB6284E270700FE835D /* MakeCowStruct.swift */; }; 40 | 9C7EA675283E98C0003E9194 /* Sema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7EA674283E98C0003E9194 /* Sema.swift */; }; 41 | 9C7EA677283E9E39003E9194 /* COWRewriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7EA676283E9E39003E9194 /* COWRewriter.swift */; }; 42 | 9C971C1D285065F60026ACA0 /* PrinterConfigs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C971C1C285065F60026ACA0 /* PrinterConfigs.swift */; }; 43 | 9C971C20285072AE0026ACA0 /* SwiftFormat in Frameworks */ = {isa = PBXBuildFile; productRef = 9C971C1F285072AE0026ACA0 /* SwiftFormat */; }; 44 | 9C971C22285072AE0026ACA0 /* SwiftFormatConfiguration in Frameworks */ = {isa = PBXBuildFile; productRef = 9C971C21285072AE0026ACA0 /* SwiftFormatConfiguration */; }; 45 | 9CA5877D285112AC001FDA92 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 9CA5877C285112AC001FDA92 /* Collections */; }; 46 | 9CA5877F285112AC001FDA92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 9CA5877E285112AC001FDA92 /* DequeModule */; }; 47 | 9CA58781285112AC001FDA92 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 9CA58780285112AC001FDA92 /* OrderedCollections */; }; 48 | 9CA5878328521407001FDA92 /* Binding+COWRewriterAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA5878228521407001FDA92 /* Binding+COWRewriterAdditions.swift */; }; 49 | 9CDEE1EE283F4147005C3A4F /* SemaTestsBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDEE1ED283F4147005C3A4F /* SemaTestsBase.swift */; }; 50 | 9CDEE1F0283FACB9005C3A4F /* SemaTypeInferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDEE1EF283FACB9005C3A4F /* SemaTypeInferTests.swift */; }; 51 | 9CDEE1F2283FACDE005C3A4F /* SemaRefactorableDeclRecognitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDEE1F1283FACDE005C3A4F /* SemaRefactorableDeclRecognitionTests.swift */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXContainerItemProxy section */ 55 | 9C0809D72838BA3000ACDCE9 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = 9C0809BD2838BA2F00ACDCE9 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = 9C0809C42838BA2F00ACDCE9; 60 | remoteInfo = COWRewriter; 61 | }; 62 | 9C0809E12838BA3000ACDCE9 /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = 9C0809BD2838BA2F00ACDCE9 /* Project object */; 65 | proxyType = 1; 66 | remoteGlobalIDString = 9C0809C42838BA2F00ACDCE9; 67 | remoteInfo = COWRewriter; 68 | }; 69 | /* End PBXContainerItemProxy section */ 70 | 71 | /* Begin PBXFileReference section */ 72 | 9C0809C52838BA2F00ACDCE9 /* Swift COW Refactorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swift COW Refactorer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | 9C0809C82838BA2F00ACDCE9 /* TheApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TheApp.swift; sourceTree = ""; }; 74 | 9C0809CA2838BA2F00ACDCE9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 75 | 9C0809CC2838BA3000ACDCE9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 76 | 9C0809CF2838BA3000ACDCE9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 77 | 9C0809D12838BA3000ACDCE9 /* COWRewriter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = COWRewriter.entitlements; sourceTree = ""; }; 78 | 9C0809D62838BA3000ACDCE9 /* COWRewriterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = COWRewriterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 9C0809E02838BA3000ACDCE9 /* COWRewriterUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = COWRewriterUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 80 | 9C0809E42838BA3000ACDCE9 /* COWRewriterUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COWRewriterUITests.swift; sourceTree = ""; }; 81 | 9C0809E62838BA3000ACDCE9 /* COWRewriterUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COWRewriterUITestsLaunchTests.swift; sourceTree = ""; }; 82 | 9C0809FE2838E6C400ACDCE9 /* FilePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePicker.swift; sourceTree = ""; }; 83 | 9C080A002838E70100ACDCE9 /* RefactorRequestsConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefactorRequestsConfigView.swift; sourceTree = ""; }; 84 | 9C080A022838FB5500ACDCE9 /* BottomToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomToolbar.swift; sourceTree = ""; }; 85 | 9C080A0D283965E500ACDCE9 /* COWRewriterTestsBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COWRewriterTestsBase.swift; sourceTree = ""; }; 86 | 9C080A0F2839EF7500ACDCE9 /* XCTestCase+COWRewriterTestsAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+COWRewriterTestsAdditions.swift"; sourceTree = ""; }; 87 | 9C080A112839F0CE00ACDCE9 /* COWRewriterWontRewriteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COWRewriterWontRewriteTests.swift; sourceTree = ""; }; 88 | 9C080A132839F0D400ACDCE9 /* COWRewritertRewriteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COWRewritertRewriteTests.swift; sourceTree = ""; }; 89 | 9C2C13DA283E743D00978CD9 /* Refactorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Refactorer.swift; sourceTree = ""; }; 90 | 9C46396C284685DF0020FCEF /* RuntimeErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeErrorHandling.swift; sourceTree = ""; }; 91 | 9C768F9C284B2EC300FE835D /* Printer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Printer.swift; sourceTree = ""; }; 92 | 9C768F9E284B47C000FE835D /* Stdlib+COWRewriterAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stdlib+COWRewriterAdditions.swift"; sourceTree = ""; }; 93 | 9C768FA0284B838A00FE835D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 94 | 9C768FA2284B83B800FE835D /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; 95 | 9C768FA4284B847200FE835D /* RefactorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefactorView.swift; sourceTree = ""; }; 96 | 9C768FA6284B89CF00FE835D /* FileDropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDropView.swift; sourceTree = ""; }; 97 | 9C768FA8284BA13C00FE835D /* ImportErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportErrorMessageView.swift; sourceTree = ""; }; 98 | 9C768FAA284BB9F500FE835D /* ConditionalKeyboardShorcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalKeyboardShorcut.swift; sourceTree = ""; }; 99 | 9C768FAF284BD2EC00FE835D /* RefactorCandidate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefactorCandidate.swift; sourceTree = ""; }; 100 | 9C768FB1284BD30200FE835D /* RefactorRequestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefactorRequestConfig.swift; sourceTree = ""; }; 101 | 9C768FB4284E267400FE835D /* MakeStorageClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeStorageClass.swift; sourceTree = ""; }; 102 | 9C768FB6284E270700FE835D /* MakeCowStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeCowStruct.swift; sourceTree = ""; }; 103 | 9C7EA674283E98C0003E9194 /* Sema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sema.swift; sourceTree = ""; }; 104 | 9C7EA676283E9E39003E9194 /* COWRewriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COWRewriter.swift; sourceTree = ""; }; 105 | 9C971C1C285065F60026ACA0 /* PrinterConfigs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrinterConfigs.swift; sourceTree = ""; }; 106 | 9CA5878228521407001FDA92 /* Binding+COWRewriterAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+COWRewriterAdditions.swift"; sourceTree = ""; }; 107 | 9CDEE1ED283F4147005C3A4F /* SemaTestsBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemaTestsBase.swift; sourceTree = ""; }; 108 | 9CDEE1EF283FACB9005C3A4F /* SemaTypeInferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemaTypeInferTests.swift; sourceTree = ""; }; 109 | 9CDEE1F1283FACDE005C3A4F /* SemaRefactorableDeclRecognitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemaRefactorableDeclRecognitionTests.swift; sourceTree = ""; }; 110 | /* End PBXFileReference section */ 111 | 112 | /* Begin PBXFrameworksBuildPhase section */ 113 | 9C0809C22838BA2F00ACDCE9 /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | 9CA5877D285112AC001FDA92 /* Collections in Frameworks */, 118 | 9C0809FB2838C53200ACDCE9 /* SwiftSyntaxParser in Frameworks */, 119 | 9C971C20285072AE0026ACA0 /* SwiftFormat in Frameworks */, 120 | 9CA5877F285112AC001FDA92 /* DequeModule in Frameworks */, 121 | 9C0809F72838C53200ACDCE9 /* SwiftSyntax in Frameworks */, 122 | 9C971C22285072AE0026ACA0 /* SwiftFormatConfiguration in Frameworks */, 123 | 9CA58781285112AC001FDA92 /* OrderedCollections in Frameworks */, 124 | 9C0809F92838C53200ACDCE9 /* SwiftSyntaxBuilder in Frameworks */, 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | 9C0809D32838BA3000ACDCE9 /* Frameworks */ = { 129 | isa = PBXFrameworksBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | 9C0809DD2838BA3000ACDCE9 /* Frameworks */ = { 136 | isa = PBXFrameworksBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXFrameworksBuildPhase section */ 143 | 144 | /* Begin PBXGroup section */ 145 | 9C0809BC2838BA2F00ACDCE9 = { 146 | isa = PBXGroup; 147 | children = ( 148 | 9C0809C72838BA2F00ACDCE9 /* COWRewriter */, 149 | 9C0809D92838BA3000ACDCE9 /* COWRewriterTests */, 150 | 9C0809E32838BA3000ACDCE9 /* COWRewriterUITests */, 151 | 9C0809C62838BA2F00ACDCE9 /* Products */, 152 | ); 153 | sourceTree = ""; 154 | }; 155 | 9C0809C62838BA2F00ACDCE9 /* Products */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 9C0809C52838BA2F00ACDCE9 /* Swift COW Refactorer.app */, 159 | 9C0809D62838BA3000ACDCE9 /* COWRewriterTests.xctest */, 160 | 9C0809E02838BA3000ACDCE9 /* COWRewriterUITests.xctest */, 161 | ); 162 | name = Products; 163 | sourceTree = ""; 164 | }; 165 | 9C0809C72838BA2F00ACDCE9 /* COWRewriter */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 9C6CE49A2849EF0C0068B319 /* UI */, 169 | 9C768FAE284BD21900FE835D /* Session */, 170 | 9C6CE4992849EF010068B319 /* Refactorer */, 171 | 9C768FB3284BDC3A00FE835D /* Printer */, 172 | 9C6CE49B2849EF1D0068B319 /* Utilities */, 173 | 9C0809CC2838BA3000ACDCE9 /* Assets.xcassets */, 174 | 9C0809D12838BA3000ACDCE9 /* COWRewriter.entitlements */, 175 | 9C0809CE2838BA3000ACDCE9 /* Preview Content */, 176 | ); 177 | path = COWRewriter; 178 | sourceTree = ""; 179 | }; 180 | 9C0809CE2838BA3000ACDCE9 /* Preview Content */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 9C0809CF2838BA3000ACDCE9 /* Preview Assets.xcassets */, 184 | ); 185 | path = "Preview Content"; 186 | sourceTree = ""; 187 | }; 188 | 9C0809D92838BA3000ACDCE9 /* COWRewriterTests */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 9C6CE4A02849F38A0068B319 /* Sema */, 192 | 9C6CE4A12849F38E0068B319 /* Reafactorer */, 193 | 9C080A0F2839EF7500ACDCE9 /* XCTestCase+COWRewriterTestsAdditions.swift */, 194 | ); 195 | path = COWRewriterTests; 196 | sourceTree = ""; 197 | }; 198 | 9C0809E32838BA3000ACDCE9 /* COWRewriterUITests */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 9C0809E42838BA3000ACDCE9 /* COWRewriterUITests.swift */, 202 | 9C0809E62838BA3000ACDCE9 /* COWRewriterUITestsLaunchTests.swift */, 203 | ); 204 | path = COWRewriterUITests; 205 | sourceTree = ""; 206 | }; 207 | 9C6CE4992849EF010068B319 /* Refactorer */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 9C2C13DA283E743D00978CD9 /* Refactorer.swift */, 211 | 9C7EA674283E98C0003E9194 /* Sema.swift */, 212 | 9C7EA676283E9E39003E9194 /* COWRewriter.swift */, 213 | 9C768FB4284E267400FE835D /* MakeStorageClass.swift */, 214 | 9C768FB6284E270700FE835D /* MakeCowStruct.swift */, 215 | ); 216 | path = Refactorer; 217 | sourceTree = ""; 218 | }; 219 | 9C6CE49A2849EF0C0068B319 /* UI */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | 9C0809C82838BA2F00ACDCE9 /* TheApp.swift */, 223 | 9C0809CA2838BA2F00ACDCE9 /* ContentView.swift */, 224 | 9C768FA4284B847200FE835D /* RefactorView.swift */, 225 | 9C768FA6284B89CF00FE835D /* FileDropView.swift */, 226 | 9C0809FE2838E6C400ACDCE9 /* FilePicker.swift */, 227 | 9C080A002838E70100ACDCE9 /* RefactorRequestsConfigView.swift */, 228 | 9C080A022838FB5500ACDCE9 /* BottomToolbar.swift */, 229 | 9C768FA8284BA13C00FE835D /* ImportErrorMessageView.swift */, 230 | ); 231 | path = UI; 232 | sourceTree = ""; 233 | }; 234 | 9C6CE49B2849EF1D0068B319 /* Utilities */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 9C46396C284685DF0020FCEF /* RuntimeErrorHandling.swift */, 238 | 9C768F9E284B47C000FE835D /* Stdlib+COWRewriterAdditions.swift */, 239 | 9C768FAA284BB9F500FE835D /* ConditionalKeyboardShorcut.swift */, 240 | 9CA5878228521407001FDA92 /* Binding+COWRewriterAdditions.swift */, 241 | ); 242 | path = Utilities; 243 | sourceTree = ""; 244 | }; 245 | 9C6CE4A02849F38A0068B319 /* Sema */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 9CDEE1ED283F4147005C3A4F /* SemaTestsBase.swift */, 249 | 9CDEE1EF283FACB9005C3A4F /* SemaTypeInferTests.swift */, 250 | 9CDEE1F1283FACDE005C3A4F /* SemaRefactorableDeclRecognitionTests.swift */, 251 | ); 252 | path = Sema; 253 | sourceTree = ""; 254 | }; 255 | 9C6CE4A12849F38E0068B319 /* Reafactorer */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 9C080A0D283965E500ACDCE9 /* COWRewriterTestsBase.swift */, 259 | 9C080A112839F0CE00ACDCE9 /* COWRewriterWontRewriteTests.swift */, 260 | 9C080A132839F0D400ACDCE9 /* COWRewritertRewriteTests.swift */, 261 | ); 262 | path = Reafactorer; 263 | sourceTree = ""; 264 | }; 265 | 9C768FAE284BD21900FE835D /* Session */ = { 266 | isa = PBXGroup; 267 | children = ( 268 | 9C768FA0284B838A00FE835D /* SessionManager.swift */, 269 | 9C768FA2284B83B800FE835D /* Session.swift */, 270 | 9C768FAF284BD2EC00FE835D /* RefactorCandidate.swift */, 271 | 9C768FB1284BD30200FE835D /* RefactorRequestConfig.swift */, 272 | ); 273 | path = Session; 274 | sourceTree = ""; 275 | }; 276 | 9C768FB3284BDC3A00FE835D /* Printer */ = { 277 | isa = PBXGroup; 278 | children = ( 279 | 9C971C1C285065F60026ACA0 /* PrinterConfigs.swift */, 280 | 9C768F9C284B2EC300FE835D /* Printer.swift */, 281 | ); 282 | path = Printer; 283 | sourceTree = ""; 284 | }; 285 | /* End PBXGroup section */ 286 | 287 | /* Begin PBXNativeTarget section */ 288 | 9C0809C42838BA2F00ACDCE9 /* COWRewriter */ = { 289 | isa = PBXNativeTarget; 290 | buildConfigurationList = 9C0809EA2838BA3000ACDCE9 /* Build configuration list for PBXNativeTarget "COWRewriter" */; 291 | buildPhases = ( 292 | 9C0809C12838BA2F00ACDCE9 /* Sources */, 293 | 9C0809C22838BA2F00ACDCE9 /* Frameworks */, 294 | 9C0809C32838BA2F00ACDCE9 /* Resources */, 295 | ); 296 | buildRules = ( 297 | ); 298 | dependencies = ( 299 | ); 300 | name = COWRewriter; 301 | packageProductDependencies = ( 302 | 9C0809F62838C53200ACDCE9 /* SwiftSyntax */, 303 | 9C0809F82838C53200ACDCE9 /* SwiftSyntaxBuilder */, 304 | 9C0809FA2838C53200ACDCE9 /* SwiftSyntaxParser */, 305 | 9C971C1F285072AE0026ACA0 /* SwiftFormat */, 306 | 9C971C21285072AE0026ACA0 /* SwiftFormatConfiguration */, 307 | 9CA5877C285112AC001FDA92 /* Collections */, 308 | 9CA5877E285112AC001FDA92 /* DequeModule */, 309 | 9CA58780285112AC001FDA92 /* OrderedCollections */, 310 | ); 311 | productName = COWRewriter; 312 | productReference = 9C0809C52838BA2F00ACDCE9 /* Swift COW Refactorer.app */; 313 | productType = "com.apple.product-type.application"; 314 | }; 315 | 9C0809D52838BA3000ACDCE9 /* COWRewriterTests */ = { 316 | isa = PBXNativeTarget; 317 | buildConfigurationList = 9C0809ED2838BA3000ACDCE9 /* Build configuration list for PBXNativeTarget "COWRewriterTests" */; 318 | buildPhases = ( 319 | 9C0809D22838BA3000ACDCE9 /* Sources */, 320 | 9C0809D32838BA3000ACDCE9 /* Frameworks */, 321 | 9C0809D42838BA3000ACDCE9 /* Resources */, 322 | ); 323 | buildRules = ( 324 | ); 325 | dependencies = ( 326 | 9C0809D82838BA3000ACDCE9 /* PBXTargetDependency */, 327 | ); 328 | name = COWRewriterTests; 329 | productName = COWRewriterTests; 330 | productReference = 9C0809D62838BA3000ACDCE9 /* COWRewriterTests.xctest */; 331 | productType = "com.apple.product-type.bundle.unit-test"; 332 | }; 333 | 9C0809DF2838BA3000ACDCE9 /* COWRewriterUITests */ = { 334 | isa = PBXNativeTarget; 335 | buildConfigurationList = 9C0809F02838BA3000ACDCE9 /* Build configuration list for PBXNativeTarget "COWRewriterUITests" */; 336 | buildPhases = ( 337 | 9C0809DC2838BA3000ACDCE9 /* Sources */, 338 | 9C0809DD2838BA3000ACDCE9 /* Frameworks */, 339 | 9C0809DE2838BA3000ACDCE9 /* Resources */, 340 | ); 341 | buildRules = ( 342 | ); 343 | dependencies = ( 344 | 9C0809E22838BA3000ACDCE9 /* PBXTargetDependency */, 345 | ); 346 | name = COWRewriterUITests; 347 | productName = COWRewriterUITests; 348 | productReference = 9C0809E02838BA3000ACDCE9 /* COWRewriterUITests.xctest */; 349 | productType = "com.apple.product-type.bundle.ui-testing"; 350 | }; 351 | /* End PBXNativeTarget section */ 352 | 353 | /* Begin PBXProject section */ 354 | 9C0809BD2838BA2F00ACDCE9 /* Project object */ = { 355 | isa = PBXProject; 356 | attributes = { 357 | BuildIndependentTargetsInParallel = 1; 358 | LastSwiftUpdateCheck = 1330; 359 | LastUpgradeCheck = 1340; 360 | TargetAttributes = { 361 | 9C0809C42838BA2F00ACDCE9 = { 362 | CreatedOnToolsVersion = 13.3.1; 363 | }; 364 | 9C0809D52838BA3000ACDCE9 = { 365 | CreatedOnToolsVersion = 13.3.1; 366 | TestTargetID = 9C0809C42838BA2F00ACDCE9; 367 | }; 368 | 9C0809DF2838BA3000ACDCE9 = { 369 | CreatedOnToolsVersion = 13.3.1; 370 | TestTargetID = 9C0809C42838BA2F00ACDCE9; 371 | }; 372 | }; 373 | }; 374 | buildConfigurationList = 9C0809C02838BA2F00ACDCE9 /* Build configuration list for PBXProject "COWRewriter" */; 375 | compatibilityVersion = "Xcode 13.0"; 376 | developmentRegion = en; 377 | hasScannedForEncodings = 0; 378 | knownRegions = ( 379 | en, 380 | Base, 381 | ); 382 | mainGroup = 9C0809BC2838BA2F00ACDCE9; 383 | packageReferences = ( 384 | 9C0809F52838C53200ACDCE9 /* XCRemoteSwiftPackageReference "swift-syntax" */, 385 | 9C971C1E285072AE0026ACA0 /* XCRemoteSwiftPackageReference "swift-format" */, 386 | 9CA5877B285112AC001FDA92 /* XCRemoteSwiftPackageReference "swift-collections" */, 387 | ); 388 | productRefGroup = 9C0809C62838BA2F00ACDCE9 /* Products */; 389 | projectDirPath = ""; 390 | projectRoot = ""; 391 | targets = ( 392 | 9C0809C42838BA2F00ACDCE9 /* COWRewriter */, 393 | 9C0809D52838BA3000ACDCE9 /* COWRewriterTests */, 394 | 9C0809DF2838BA3000ACDCE9 /* COWRewriterUITests */, 395 | ); 396 | }; 397 | /* End PBXProject section */ 398 | 399 | /* Begin PBXResourcesBuildPhase section */ 400 | 9C0809C32838BA2F00ACDCE9 /* Resources */ = { 401 | isa = PBXResourcesBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | 9C0809D02838BA3000ACDCE9 /* Preview Assets.xcassets in Resources */, 405 | 9C0809CD2838BA3000ACDCE9 /* Assets.xcassets in Resources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | 9C0809D42838BA3000ACDCE9 /* Resources */ = { 410 | isa = PBXResourcesBuildPhase; 411 | buildActionMask = 2147483647; 412 | files = ( 413 | ); 414 | runOnlyForDeploymentPostprocessing = 0; 415 | }; 416 | 9C0809DE2838BA3000ACDCE9 /* Resources */ = { 417 | isa = PBXResourcesBuildPhase; 418 | buildActionMask = 2147483647; 419 | files = ( 420 | ); 421 | runOnlyForDeploymentPostprocessing = 0; 422 | }; 423 | /* End PBXResourcesBuildPhase section */ 424 | 425 | /* Begin PBXSourcesBuildPhase section */ 426 | 9C0809C12838BA2F00ACDCE9 /* Sources */ = { 427 | isa = PBXSourcesBuildPhase; 428 | buildActionMask = 2147483647; 429 | files = ( 430 | 9C768F9D284B2EC300FE835D /* Printer.swift in Sources */, 431 | 9C768FA9284BA13C00FE835D /* ImportErrorMessageView.swift in Sources */, 432 | 9C768FB7284E270700FE835D /* MakeCowStruct.swift in Sources */, 433 | 9CA5878328521407001FDA92 /* Binding+COWRewriterAdditions.swift in Sources */, 434 | 9C080A032838FB5500ACDCE9 /* BottomToolbar.swift in Sources */, 435 | 9C46396D284685DF0020FCEF /* RuntimeErrorHandling.swift in Sources */, 436 | 9C0809CB2838BA2F00ACDCE9 /* ContentView.swift in Sources */, 437 | 9C768FA7284B89CF00FE835D /* FileDropView.swift in Sources */, 438 | 9C0809FF2838E6C400ACDCE9 /* FilePicker.swift in Sources */, 439 | 9C768FAB284BB9F500FE835D /* ConditionalKeyboardShorcut.swift in Sources */, 440 | 9C7EA675283E98C0003E9194 /* Sema.swift in Sources */, 441 | 9C0809C92838BA2F00ACDCE9 /* TheApp.swift in Sources */, 442 | 9C768FB2284BD30200FE835D /* RefactorRequestConfig.swift in Sources */, 443 | 9C768FA3284B83B800FE835D /* Session.swift in Sources */, 444 | 9C7EA677283E9E39003E9194 /* COWRewriter.swift in Sources */, 445 | 9C768FA5284B847200FE835D /* RefactorView.swift in Sources */, 446 | 9C2C13DB283E743D00978CD9 /* Refactorer.swift in Sources */, 447 | 9C768FA1284B838A00FE835D /* SessionManager.swift in Sources */, 448 | 9C768FB5284E267400FE835D /* MakeStorageClass.swift in Sources */, 449 | 9C080A012838E70100ACDCE9 /* RefactorRequestsConfigView.swift in Sources */, 450 | 9C768F9F284B47C000FE835D /* Stdlib+COWRewriterAdditions.swift in Sources */, 451 | 9C768FB0284BD2EC00FE835D /* RefactorCandidate.swift in Sources */, 452 | 9C971C1D285065F60026ACA0 /* PrinterConfigs.swift in Sources */, 453 | ); 454 | runOnlyForDeploymentPostprocessing = 0; 455 | }; 456 | 9C0809D22838BA3000ACDCE9 /* Sources */ = { 457 | isa = PBXSourcesBuildPhase; 458 | buildActionMask = 2147483647; 459 | files = ( 460 | 9CDEE1F0283FACB9005C3A4F /* SemaTypeInferTests.swift in Sources */, 461 | 9C080A102839EF7500ACDCE9 /* XCTestCase+COWRewriterTestsAdditions.swift in Sources */, 462 | 9CDEE1F2283FACDE005C3A4F /* SemaRefactorableDeclRecognitionTests.swift in Sources */, 463 | 9C080A0E283965E500ACDCE9 /* COWRewriterTestsBase.swift in Sources */, 464 | 9C080A122839F0CE00ACDCE9 /* COWRewriterWontRewriteTests.swift in Sources */, 465 | 9C080A142839F0D400ACDCE9 /* COWRewritertRewriteTests.swift in Sources */, 466 | 9CDEE1EE283F4147005C3A4F /* SemaTestsBase.swift in Sources */, 467 | ); 468 | runOnlyForDeploymentPostprocessing = 0; 469 | }; 470 | 9C0809DC2838BA3000ACDCE9 /* Sources */ = { 471 | isa = PBXSourcesBuildPhase; 472 | buildActionMask = 2147483647; 473 | files = ( 474 | 9C0809E72838BA3000ACDCE9 /* COWRewriterUITestsLaunchTests.swift in Sources */, 475 | 9C0809E52838BA3000ACDCE9 /* COWRewriterUITests.swift in Sources */, 476 | ); 477 | runOnlyForDeploymentPostprocessing = 0; 478 | }; 479 | /* End PBXSourcesBuildPhase section */ 480 | 481 | /* Begin PBXTargetDependency section */ 482 | 9C0809D82838BA3000ACDCE9 /* PBXTargetDependency */ = { 483 | isa = PBXTargetDependency; 484 | target = 9C0809C42838BA2F00ACDCE9 /* COWRewriter */; 485 | targetProxy = 9C0809D72838BA3000ACDCE9 /* PBXContainerItemProxy */; 486 | }; 487 | 9C0809E22838BA3000ACDCE9 /* PBXTargetDependency */ = { 488 | isa = PBXTargetDependency; 489 | target = 9C0809C42838BA2F00ACDCE9 /* COWRewriter */; 490 | targetProxy = 9C0809E12838BA3000ACDCE9 /* PBXContainerItemProxy */; 491 | }; 492 | /* End PBXTargetDependency section */ 493 | 494 | /* Begin XCBuildConfiguration section */ 495 | 9C0809E82838BA3000ACDCE9 /* Debug */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | ALWAYS_SEARCH_USER_PATHS = NO; 499 | CLANG_ANALYZER_NONNULL = YES; 500 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 501 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 502 | CLANG_ENABLE_MODULES = YES; 503 | CLANG_ENABLE_OBJC_ARC = YES; 504 | CLANG_ENABLE_OBJC_WEAK = YES; 505 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 506 | CLANG_WARN_BOOL_CONVERSION = YES; 507 | CLANG_WARN_COMMA = YES; 508 | CLANG_WARN_CONSTANT_CONVERSION = YES; 509 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 510 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 511 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 512 | CLANG_WARN_EMPTY_BODY = YES; 513 | CLANG_WARN_ENUM_CONVERSION = YES; 514 | CLANG_WARN_INFINITE_RECURSION = YES; 515 | CLANG_WARN_INT_CONVERSION = YES; 516 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 517 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 518 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 519 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 520 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 521 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 522 | CLANG_WARN_STRICT_PROTOTYPES = YES; 523 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 524 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 525 | CLANG_WARN_UNREACHABLE_CODE = YES; 526 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 527 | COPY_PHASE_STRIP = NO; 528 | DEBUG_INFORMATION_FORMAT = dwarf; 529 | ENABLE_STRICT_OBJC_MSGSEND = YES; 530 | ENABLE_TESTABILITY = YES; 531 | GCC_C_LANGUAGE_STANDARD = gnu11; 532 | GCC_DYNAMIC_NO_PIC = NO; 533 | GCC_NO_COMMON_BLOCKS = YES; 534 | GCC_OPTIMIZATION_LEVEL = 0; 535 | GCC_PREPROCESSOR_DEFINITIONS = ( 536 | "DEBUG=1", 537 | "$(inherited)", 538 | ); 539 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 540 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 541 | GCC_WARN_UNDECLARED_SELECTOR = YES; 542 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 543 | GCC_WARN_UNUSED_FUNCTION = YES; 544 | GCC_WARN_UNUSED_VARIABLE = YES; 545 | MACOSX_DEPLOYMENT_TARGET = 12.3; 546 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 547 | MTL_FAST_MATH = YES; 548 | ONLY_ACTIVE_ARCH = YES; 549 | SDKROOT = macosx; 550 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 551 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 552 | }; 553 | name = Debug; 554 | }; 555 | 9C0809E92838BA3000ACDCE9 /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ALWAYS_SEARCH_USER_PATHS = NO; 559 | CLANG_ANALYZER_NONNULL = YES; 560 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 561 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 562 | CLANG_ENABLE_MODULES = YES; 563 | CLANG_ENABLE_OBJC_ARC = YES; 564 | CLANG_ENABLE_OBJC_WEAK = YES; 565 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 566 | CLANG_WARN_BOOL_CONVERSION = YES; 567 | CLANG_WARN_COMMA = YES; 568 | CLANG_WARN_CONSTANT_CONVERSION = YES; 569 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 570 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 571 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 572 | CLANG_WARN_EMPTY_BODY = YES; 573 | CLANG_WARN_ENUM_CONVERSION = YES; 574 | CLANG_WARN_INFINITE_RECURSION = YES; 575 | CLANG_WARN_INT_CONVERSION = YES; 576 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 577 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 578 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 579 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 580 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 581 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 582 | CLANG_WARN_STRICT_PROTOTYPES = YES; 583 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 584 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 585 | CLANG_WARN_UNREACHABLE_CODE = YES; 586 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 587 | COPY_PHASE_STRIP = NO; 588 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 589 | ENABLE_NS_ASSERTIONS = NO; 590 | ENABLE_STRICT_OBJC_MSGSEND = YES; 591 | GCC_C_LANGUAGE_STANDARD = gnu11; 592 | GCC_NO_COMMON_BLOCKS = YES; 593 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 594 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 595 | GCC_WARN_UNDECLARED_SELECTOR = YES; 596 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 597 | GCC_WARN_UNUSED_FUNCTION = YES; 598 | GCC_WARN_UNUSED_VARIABLE = YES; 599 | MACOSX_DEPLOYMENT_TARGET = 12.3; 600 | MTL_ENABLE_DEBUG_INFO = NO; 601 | MTL_FAST_MATH = YES; 602 | SDKROOT = macosx; 603 | SWIFT_COMPILATION_MODE = wholemodule; 604 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 605 | }; 606 | name = Release; 607 | }; 608 | 9C0809EB2838BA3000ACDCE9 /* Debug */ = { 609 | isa = XCBuildConfiguration; 610 | buildSettings = { 611 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 612 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 613 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 614 | CODE_SIGN_ENTITLEMENTS = COWRewriter/COWRewriter.entitlements; 615 | CODE_SIGN_IDENTITY = "-"; 616 | CODE_SIGN_STYLE = Automatic; 617 | COMBINE_HIDPI_IMAGES = YES; 618 | CURRENT_PROJECT_VERSION = 1; 619 | DEVELOPMENT_ASSET_PATHS = "\"COWRewriter/Preview Content\""; 620 | ENABLE_HARDENED_RUNTIME = NO; 621 | ENABLE_PREVIEWS = YES; 622 | GENERATE_INFOPLIST_FILE = YES; 623 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 624 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 625 | LD_RUNPATH_SEARCH_PATHS = ( 626 | "$(inherited)", 627 | "@executable_path/../Frameworks", 628 | ); 629 | MACOSX_DEPLOYMENT_TARGET = 12.0; 630 | MARKETING_VERSION = 1.0; 631 | PRODUCT_BUNDLE_IDENTIFIER = "com.WeZZardDesign.Swift-COW-Refactor"; 632 | PRODUCT_NAME = "Swift COW Refactorer"; 633 | SWIFT_EMIT_LOC_STRINGS = YES; 634 | SWIFT_VERSION = 5.0; 635 | }; 636 | name = Debug; 637 | }; 638 | 9C0809EC2838BA3000ACDCE9 /* Release */ = { 639 | isa = XCBuildConfiguration; 640 | buildSettings = { 641 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 642 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 643 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 644 | CODE_SIGN_ENTITLEMENTS = COWRewriter/COWRewriter.entitlements; 645 | CODE_SIGN_IDENTITY = "-"; 646 | CODE_SIGN_STYLE = Automatic; 647 | COMBINE_HIDPI_IMAGES = YES; 648 | CURRENT_PROJECT_VERSION = 1; 649 | DEVELOPMENT_ASSET_PATHS = "\"COWRewriter/Preview Content\""; 650 | ENABLE_HARDENED_RUNTIME = NO; 651 | ENABLE_PREVIEWS = YES; 652 | GENERATE_INFOPLIST_FILE = YES; 653 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 654 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 655 | LD_RUNPATH_SEARCH_PATHS = ( 656 | "$(inherited)", 657 | "@executable_path/../Frameworks", 658 | ); 659 | MACOSX_DEPLOYMENT_TARGET = 12.0; 660 | MARKETING_VERSION = 1.0; 661 | PRODUCT_BUNDLE_IDENTIFIER = "com.WeZZardDesign.Swift-COW-Refactor"; 662 | PRODUCT_NAME = "Swift COW Refactorer"; 663 | SWIFT_EMIT_LOC_STRINGS = YES; 664 | SWIFT_VERSION = 5.0; 665 | }; 666 | name = Release; 667 | }; 668 | 9C0809EE2838BA3000ACDCE9 /* Debug */ = { 669 | isa = XCBuildConfiguration; 670 | buildSettings = { 671 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 672 | BUNDLE_LOADER = "$(TEST_HOST)"; 673 | CODE_SIGN_STYLE = Automatic; 674 | CURRENT_PROJECT_VERSION = 1; 675 | GENERATE_INFOPLIST_FILE = YES; 676 | MACOSX_DEPLOYMENT_TARGET = 12.3; 677 | MARKETING_VERSION = 1.0; 678 | PRODUCT_BUNDLE_IDENTIFIER = com.WeZZardDesign.COWRewriterTests; 679 | PRODUCT_NAME = "$(TARGET_NAME)"; 680 | SWIFT_EMIT_LOC_STRINGS = NO; 681 | SWIFT_VERSION = 5.0; 682 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/COWRewriter.app/Contents/MacOS/COWRewriter"; 683 | }; 684 | name = Debug; 685 | }; 686 | 9C0809EF2838BA3000ACDCE9 /* Release */ = { 687 | isa = XCBuildConfiguration; 688 | buildSettings = { 689 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 690 | BUNDLE_LOADER = "$(TEST_HOST)"; 691 | CODE_SIGN_STYLE = Automatic; 692 | CURRENT_PROJECT_VERSION = 1; 693 | GENERATE_INFOPLIST_FILE = YES; 694 | MACOSX_DEPLOYMENT_TARGET = 12.3; 695 | MARKETING_VERSION = 1.0; 696 | PRODUCT_BUNDLE_IDENTIFIER = com.WeZZardDesign.COWRewriterTests; 697 | PRODUCT_NAME = "$(TARGET_NAME)"; 698 | SWIFT_EMIT_LOC_STRINGS = NO; 699 | SWIFT_VERSION = 5.0; 700 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/COWRewriter.app/Contents/MacOS/COWRewriter"; 701 | }; 702 | name = Release; 703 | }; 704 | 9C0809F12838BA3000ACDCE9 /* Debug */ = { 705 | isa = XCBuildConfiguration; 706 | buildSettings = { 707 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 708 | CODE_SIGN_STYLE = Automatic; 709 | CURRENT_PROJECT_VERSION = 1; 710 | GENERATE_INFOPLIST_FILE = YES; 711 | MARKETING_VERSION = 1.0; 712 | PRODUCT_BUNDLE_IDENTIFIER = com.WeZZardDesign.COWRewriterUITests; 713 | PRODUCT_NAME = "$(TARGET_NAME)"; 714 | SWIFT_EMIT_LOC_STRINGS = NO; 715 | SWIFT_VERSION = 5.0; 716 | TEST_TARGET_NAME = COWRewriter; 717 | }; 718 | name = Debug; 719 | }; 720 | 9C0809F22838BA3000ACDCE9 /* Release */ = { 721 | isa = XCBuildConfiguration; 722 | buildSettings = { 723 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 724 | CODE_SIGN_STYLE = Automatic; 725 | CURRENT_PROJECT_VERSION = 1; 726 | GENERATE_INFOPLIST_FILE = YES; 727 | MARKETING_VERSION = 1.0; 728 | PRODUCT_BUNDLE_IDENTIFIER = com.WeZZardDesign.COWRewriterUITests; 729 | PRODUCT_NAME = "$(TARGET_NAME)"; 730 | SWIFT_EMIT_LOC_STRINGS = NO; 731 | SWIFT_VERSION = 5.0; 732 | TEST_TARGET_NAME = COWRewriter; 733 | }; 734 | name = Release; 735 | }; 736 | /* End XCBuildConfiguration section */ 737 | 738 | /* Begin XCConfigurationList section */ 739 | 9C0809C02838BA2F00ACDCE9 /* Build configuration list for PBXProject "COWRewriter" */ = { 740 | isa = XCConfigurationList; 741 | buildConfigurations = ( 742 | 9C0809E82838BA3000ACDCE9 /* Debug */, 743 | 9C0809E92838BA3000ACDCE9 /* Release */, 744 | ); 745 | defaultConfigurationIsVisible = 0; 746 | defaultConfigurationName = Release; 747 | }; 748 | 9C0809EA2838BA3000ACDCE9 /* Build configuration list for PBXNativeTarget "COWRewriter" */ = { 749 | isa = XCConfigurationList; 750 | buildConfigurations = ( 751 | 9C0809EB2838BA3000ACDCE9 /* Debug */, 752 | 9C0809EC2838BA3000ACDCE9 /* Release */, 753 | ); 754 | defaultConfigurationIsVisible = 0; 755 | defaultConfigurationName = Release; 756 | }; 757 | 9C0809ED2838BA3000ACDCE9 /* Build configuration list for PBXNativeTarget "COWRewriterTests" */ = { 758 | isa = XCConfigurationList; 759 | buildConfigurations = ( 760 | 9C0809EE2838BA3000ACDCE9 /* Debug */, 761 | 9C0809EF2838BA3000ACDCE9 /* Release */, 762 | ); 763 | defaultConfigurationIsVisible = 0; 764 | defaultConfigurationName = Release; 765 | }; 766 | 9C0809F02838BA3000ACDCE9 /* Build configuration list for PBXNativeTarget "COWRewriterUITests" */ = { 767 | isa = XCConfigurationList; 768 | buildConfigurations = ( 769 | 9C0809F12838BA3000ACDCE9 /* Debug */, 770 | 9C0809F22838BA3000ACDCE9 /* Release */, 771 | ); 772 | defaultConfigurationIsVisible = 0; 773 | defaultConfigurationName = Release; 774 | }; 775 | /* End XCConfigurationList section */ 776 | 777 | /* Begin XCRemoteSwiftPackageReference section */ 778 | 9C0809F52838C53200ACDCE9 /* XCRemoteSwiftPackageReference "swift-syntax" */ = { 779 | isa = XCRemoteSwiftPackageReference; 780 | repositoryURL = "https://github.com/apple/swift-syntax.git"; 781 | requirement = { 782 | kind = exactVersion; 783 | version = 0.50600.1; 784 | }; 785 | }; 786 | 9C971C1E285072AE0026ACA0 /* XCRemoteSwiftPackageReference "swift-format" */ = { 787 | isa = XCRemoteSwiftPackageReference; 788 | repositoryURL = "https://github.com/apple/swift-format"; 789 | requirement = { 790 | kind = exactVersion; 791 | version = 0.50600.1; 792 | }; 793 | }; 794 | 9CA5877B285112AC001FDA92 /* XCRemoteSwiftPackageReference "swift-collections" */ = { 795 | isa = XCRemoteSwiftPackageReference; 796 | repositoryURL = "https://github.com/apple/swift-collections.git"; 797 | requirement = { 798 | kind = upToNextMajorVersion; 799 | minimumVersion = 1.0.0; 800 | }; 801 | }; 802 | /* End XCRemoteSwiftPackageReference section */ 803 | 804 | /* Begin XCSwiftPackageProductDependency section */ 805 | 9C0809F62838C53200ACDCE9 /* SwiftSyntax */ = { 806 | isa = XCSwiftPackageProductDependency; 807 | package = 9C0809F52838C53200ACDCE9 /* XCRemoteSwiftPackageReference "swift-syntax" */; 808 | productName = SwiftSyntax; 809 | }; 810 | 9C0809F82838C53200ACDCE9 /* SwiftSyntaxBuilder */ = { 811 | isa = XCSwiftPackageProductDependency; 812 | package = 9C0809F52838C53200ACDCE9 /* XCRemoteSwiftPackageReference "swift-syntax" */; 813 | productName = SwiftSyntaxBuilder; 814 | }; 815 | 9C0809FA2838C53200ACDCE9 /* SwiftSyntaxParser */ = { 816 | isa = XCSwiftPackageProductDependency; 817 | package = 9C0809F52838C53200ACDCE9 /* XCRemoteSwiftPackageReference "swift-syntax" */; 818 | productName = SwiftSyntaxParser; 819 | }; 820 | 9C971C1F285072AE0026ACA0 /* SwiftFormat */ = { 821 | isa = XCSwiftPackageProductDependency; 822 | package = 9C971C1E285072AE0026ACA0 /* XCRemoteSwiftPackageReference "swift-format" */; 823 | productName = SwiftFormat; 824 | }; 825 | 9C971C21285072AE0026ACA0 /* SwiftFormatConfiguration */ = { 826 | isa = XCSwiftPackageProductDependency; 827 | package = 9C971C1E285072AE0026ACA0 /* XCRemoteSwiftPackageReference "swift-format" */; 828 | productName = SwiftFormatConfiguration; 829 | }; 830 | 9CA5877C285112AC001FDA92 /* Collections */ = { 831 | isa = XCSwiftPackageProductDependency; 832 | package = 9CA5877B285112AC001FDA92 /* XCRemoteSwiftPackageReference "swift-collections" */; 833 | productName = Collections; 834 | }; 835 | 9CA5877E285112AC001FDA92 /* DequeModule */ = { 836 | isa = XCSwiftPackageProductDependency; 837 | package = 9CA5877B285112AC001FDA92 /* XCRemoteSwiftPackageReference "swift-collections" */; 838 | productName = DequeModule; 839 | }; 840 | 9CA58780285112AC001FDA92 /* OrderedCollections */ = { 841 | isa = XCSwiftPackageProductDependency; 842 | package = 9CA5877B285112AC001FDA92 /* XCRemoteSwiftPackageReference "swift-collections" */; 843 | productName = OrderedCollections; 844 | }; 845 | /* End XCSwiftPackageProductDependency section */ 846 | }; 847 | rootObject = 9C0809BD2838BA2F00ACDCE9 /* Project object */; 848 | } 849 | -------------------------------------------------------------------------------- /COWRewriter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /COWRewriter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /COWRewriter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-argument-parser", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-argument-parser.git", 7 | "state" : { 8 | "revision" : "f3c9084a71ef4376f2fabbdf1d3d90a49f1fabdb", 9 | "version" : "1.1.2" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-collections", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/apple/swift-collections.git", 16 | "state" : { 17 | "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb", 18 | "version" : "1.0.2" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-format", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/apple/swift-format", 25 | "state" : { 26 | "revision" : "e6b8c60c7671066d229e30efa1e31acf57be412e", 27 | "version" : "0.50600.1" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-syntax", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/apple/swift-syntax.git", 34 | "state" : { 35 | "revision" : "0b6c22b97f8e9320bca62e82cdbee601cf37ad3f", 36 | "version" : "0.50600.1" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-system", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/apple/swift-system.git", 43 | "state" : { 44 | "revision" : "836bc4557b74fe6d2660218d56e3ce96aff76574", 45 | "version" : "1.1.1" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-tools-support-core", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/apple/swift-tools-support-core.git", 52 | "state" : { 53 | "revision" : "b7667f3e266af621e5cc9c77e74cacd8e8c00cb4", 54 | "version" : "0.2.5" 55 | } 56 | } 57 | ], 58 | "version" : 2 59 | } 60 | -------------------------------------------------------------------------------- /COWRewriter.xcodeproj/xcshareddata/xcschemes/COWRewriter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "Icon-32.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon-33.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "Icon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon-129.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "Icon-256.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon-257.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "Icon-512.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon-513.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "Icon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-129.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-129.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-16.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-256.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-257.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-32.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-33.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-512.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-513.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/COWRewriter/Assets.xcassets/AppIcon.appiconset/Icon-64.png -------------------------------------------------------------------------------- /COWRewriter/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /COWRewriter/COWRewriter.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /COWRewriter/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /COWRewriter/Printer/Printer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Printer.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | import SwiftFormat 11 | import SwiftFormatCore 12 | import SwiftFormatConfiguration 13 | import SwiftFormatPrettyPrint 14 | 15 | class Printer { 16 | 17 | var configs: PrinterConfigs 18 | 19 | init(configs: PrinterConfigs = PrinterConfigs()) { 20 | self.configs = configs 21 | } 22 | 23 | func print(syntax: SourceFileSyntax, url: URL) -> String { 24 | var configuration = Configuration() 25 | configuration.tabWidth = configs.tabWidth 26 | switch configs.indentationMode { 27 | case .space: 28 | configuration.indentation = .spaces(configs.indentWidth) 29 | case .tab: 30 | configuration.indentation = .tabs(configs.indentWidth) 31 | } 32 | 33 | let context = Context( 34 | configuration: configuration, 35 | findingConsumer: nil, 36 | fileURL: url, 37 | sourceFileSyntax: syntax, 38 | source: nil, 39 | ruleNameCache: [:] 40 | ) 41 | 42 | let operatorContext = OperatorContext.makeBuiltinOperatorContext() 43 | let printer = PrettyPrinter( 44 | context: context, 45 | operatorContext: operatorContext, 46 | node: Syntax(syntax), 47 | printTokenStream: false, 48 | whitespaceOnly: false) 49 | return printer.prettyPrint() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /COWRewriter/Printer/PrinterConfigs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrinterConfigs.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/8/22. 6 | // 7 | 8 | struct PrinterConfigs: Equatable { 9 | 10 | enum IndentationMode: Hashable, CaseIterable { 11 | 12 | case tab 13 | 14 | case space 15 | 16 | var displayName: String { 17 | switch self { 18 | case .tab: return "Tabs" 19 | case .space: return "Spaces" 20 | } 21 | } 22 | 23 | } 24 | 25 | var indentationMode: IndentationMode 26 | 27 | var tabWidth: Int 28 | 29 | var indentWidth: Int 30 | 31 | init() { 32 | indentationMode = .space 33 | tabWidth = 2 34 | indentWidth = 4 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /COWRewriter/Refactorer/COWRewriter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // COWRewriter.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/26/22. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftSyntaxBuilder 10 | import Collections 11 | 12 | protocol COWRewriterInputContext: AnyObject { 13 | 14 | var file: String? { get } 15 | 16 | var tree: SourceFileSyntax { get } 17 | 18 | var slc: SourceLocationConverter { get } 19 | 20 | } 21 | 22 | enum COWRewriterError: Error { 23 | 24 | case noInferredTypeAndUserType(node: StructDeclSyntax, storageName: String) 25 | 26 | } 27 | 28 | class COWRewriter { 29 | 30 | unowned let input: COWRewriterInputContext 31 | 32 | private(set) var errors: [COWRewriterError] 33 | 34 | init(input: COWRewriterInputContext) { 35 | self.input = input 36 | self.errors = [] 37 | } 38 | 39 | func execute(requests: [RefactorRequest]) -> SourceFileSyntax { 40 | let concrete = COWRewriterConcrete(slc: input.slc, requests: requests) 41 | return SourceFileSyntax(concrete.visit(input.tree)) ?? input.tree 42 | } 43 | 44 | } 45 | 46 | /** 47 | --- Create Storage Class ------------------------------------------------------ 48 | - Collect struct nested types -> resolve final name for the `Storage` class. 49 | - Collect struct stored properties. 50 | - `Storage`'s memberwise initializer shall take default value into consideration. 51 | ------------------------------------------------------------------------------- 52 | 1. Create a storage class, say `Storage`. 53 | 2. Copy all the stored property of the `struct` to `Storage`. 54 | 3. Collect all initializers of the `struct` and copy to `Storage`. 55 | 4. Create the memberwrise initializer for `Storage` if needed. 56 | 5. Create a copy initializer for `Storage`. 57 | --- Rewrite Struct Decl ------------------------------------------------------- 58 | - Collect struct members 59 | -> resolve final name for the `storage` variable. 60 | -> resolve final name for the `makeUniquelyReferencedStorage` function. 61 | -> resolve if it is necessary to create the memberwise initializer 62 | -> resolve how many initializers here are needed to be copied to `Storage` 63 | ------------------------------------------------------------------------------- 64 | 6. Create a storage stored property in `struct`, say `storage`. 65 | 7. Create a storage unique-ify function, say `makeUniquelyReferencedStorage`, in `struct`. 66 | 8. Rewrite all the stored properties in `struct` (except the `storage`) with dispatch call to relative properties in `storage` 67 | 9. Create the memberwrise initializer for `struct` if needed. 68 | 10. Rewrite all the initializers in `struct` with dispatch call to relative initializers in `Storage` 69 | */ 70 | 71 | private class COWRewriterConcrete: SyntaxRewriter { 72 | 73 | let slc: SourceLocationConverter 74 | 75 | let requests: [RefactorRequest] 76 | 77 | private(set) var errors: [COWRewriterError] 78 | 79 | init(slc: SourceLocationConverter, requests: [RefactorRequest]) { 80 | self.slc = slc 81 | self.requests = requests 82 | self.errors = [] 83 | } 84 | 85 | override func visit(_ node: StructDeclSyntax) -> DeclSyntax { 86 | guard let request = requests.firstRequest(for: node.sourceRange(converter: slc)) else { 87 | return super.visit(node) 88 | } 89 | 90 | let resolvedStorageNameAndTypes = resolveStorageNameAndTypes( 91 | for: node, 92 | with: request.typedefs, 93 | errors: &self.errors 94 | ) 95 | 96 | let storageClass = makeStorageClass( 97 | structDecl: node, 98 | className: request.storageClassName, 99 | resolvedStorageNameAndTypes: resolvedStorageNameAndTypes 100 | ) 101 | 102 | let refactoredSyntax = makeCowStruct( 103 | originalStructDecl: node, 104 | storageClass: storageClass, 105 | storageVariableName: request.storageVariableName, 106 | storageUniquificationFunctionName: request.storageUniquificationFunctionName, 107 | resolvedStorageNameAndTypes: resolvedStorageNameAndTypes 108 | ) 109 | 110 | return DeclSyntax(refactoredSyntax) 111 | } 112 | 113 | } 114 | 115 | private func resolveStorageNameAndTypes( 116 | for structDecl: StructDeclSyntax, 117 | with userTypeForStorageName: [String : TypeSyntax], 118 | errors: inout [COWRewriterError] 119 | ) -> OrderedDictionary { 120 | let storedVariables = structDecl.members.members.storedVariables 121 | let storageNamesAndTypes = OrderedDictionary( 122 | uniqueKeysWithValues: storedVariables.flatMap(\.allIdentifiersAndTypes) 123 | ) 124 | 125 | var resolvedStorageNameAndTypes = OrderedDictionary() 126 | for (storageName, typeOrNil) in storageNamesAndTypes { 127 | let userTypeOrNil = userTypeForStorageName[storageName] 128 | let resolvedTypeOrNil = typeOrNil ?? userTypeOrNil 129 | guard let resolvedType = resolvedTypeOrNil else { 130 | errors.append(COWRewriterError.noInferredTypeAndUserType(node: structDecl, storageName: storageName)) 131 | continue 132 | } 133 | resolvedStorageNameAndTypes[storageName] = resolvedType 134 | } 135 | return resolvedStorageNameAndTypes 136 | } 137 | 138 | extension Sequence where Element == RefactorRequest { 139 | 140 | func firstRequest(for sourceRange: SourceRange) -> RefactorRequest? { 141 | for each in self where each.decl.sourceRange == sourceRange { 142 | return each 143 | } 144 | return nil 145 | } 146 | 147 | } 148 | 149 | extension MemberDeclListSyntax { 150 | 151 | var storedVariables: [VariableDeclSyntax] { 152 | compactMap { member -> VariableDeclSyntax? in 153 | guard let variableDecl = member.decl.as(VariableDeclSyntax.self), 154 | variableDecl.isStored else { 155 | return nil 156 | } 157 | return variableDecl 158 | } 159 | } 160 | 161 | var initializers: [InitializerDeclSyntax] { 162 | compactMap { member -> InitializerDeclSyntax? in 163 | guard let initDecl = member.decl.as(InitializerDeclSyntax.self) else { 164 | return nil 165 | } 166 | return initDecl 167 | } 168 | } 169 | 170 | } 171 | 172 | extension VariableDeclSyntax { 173 | 174 | var isStored: Bool { 175 | if letOrVarKeyword == .let { 176 | return true 177 | } 178 | return bindings.reduce(true) { (partial, binding) in 179 | partial && binding.accessor == nil 180 | } 181 | } 182 | 183 | } 184 | 185 | extension VariableDeclSyntax { 186 | 187 | fileprivate var allIdentifiersAndTypes: [String : TypeSyntax?] { 188 | let allIdentifiersAndTypes = bindings 189 | .compactMap { binding -> (String, TypeSyntax?)? in 190 | guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { 191 | return nil 192 | } 193 | 194 | let type = binding.typeAnnotation?.type 195 | 196 | return (identifierPattern.identifier.text, type) 197 | } 198 | return Dictionary(uniqueKeysWithValues: allIdentifiersAndTypes) 199 | } 200 | 201 | } 202 | 203 | -------------------------------------------------------------------------------- /COWRewriter/Refactorer/MakeCowStruct.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MakeCowStruct.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/6/22. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftSyntaxBuilder 10 | import Collections 11 | 12 | func makeCowStruct( 13 | originalStructDecl: StructDeclSyntax, 14 | storageClass: ClassDeclSyntax, 15 | storageVariableName: String, 16 | storageUniquificationFunctionName: String, 17 | resolvedStorageNameAndTypes: OrderedDictionary 18 | ) -> StructDeclSyntax { 19 | originalStructDecl.withMembers( 20 | MemberDeclBlockSyntax { memberDeclBlock in 21 | memberDeclBlock.useLeftBrace(.leftBrace) 22 | // class Storage { ... } 23 | memberDeclBlock.addMember( 24 | MemberDeclListItemSyntax { item in 25 | item.useDecl(DeclSyntax(storageClass)) 26 | } 27 | ) 28 | // var storage: Storage 29 | memberDeclBlock.addMember( 30 | MemberDeclListItemSyntax { item in 31 | item.useDecl( 32 | DeclSyntax( 33 | VariableDeclSyntax { variableDecl in 34 | variableDecl.addModifier( 35 | DeclModifierSyntax { declModifier in 36 | declModifier.useName(.private) 37 | } 38 | ) 39 | variableDecl.useLetOrVarKeyword(.var) 40 | variableDecl.addBinding( 41 | PatternBindingSyntax { patternBinding in 42 | patternBinding.usePattern( 43 | PatternSyntax( 44 | IdentifierPatternSyntax { identifierPattern in 45 | identifierPattern.useIdentifier(.identifier(storageVariableName)) 46 | } 47 | ) 48 | ) 49 | patternBinding.useTypeAnnotation( 50 | TypeAnnotationSyntax { typeAnnotation in 51 | typeAnnotation.useColon(.colon) 52 | typeAnnotation.useType( 53 | TypeSyntax( 54 | SimpleTypeIdentifierSyntax { simpleTypeId in 55 | simpleTypeId.useName(.identifier(storageClass.identifier.text)) 56 | } 57 | ) 58 | ) 59 | } 60 | ) 61 | } 62 | ) 63 | }.withLeadingTrivia(.carriageReturnLineFeeds(2)) 64 | ) 65 | ) 66 | } 67 | ) 68 | // func makeUniqueStorageIfNeeded 69 | memberDeclBlock.addMember( 70 | MemberDeclListItemSyntax { item in 71 | item.useDecl( 72 | DeclSyntax( 73 | makeStorageUniquificationFunctionDecl( 74 | functionName: storageUniquificationFunctionName, 75 | storageClassName: storageClass.identifier.text, 76 | storageVariableName: storageVariableName 77 | ).withLeadingTrivia(.carriageReturnLineFeeds(2)) 78 | ) 79 | ) 80 | } 81 | ) 82 | // var foo: Bar { 83 | // _read { 84 | // ... 85 | // } 86 | // _modify { 87 | // ... 88 | // } 89 | // } 90 | for eachStoredVariable in originalStructDecl.members.members.storedVariables { 91 | let dispatchedVariableDecls = makeStorageDispatchedVariableDecls( 92 | storedPropertyVariableDecl: eachStoredVariable, 93 | storageVariableName: storageVariableName, 94 | storageUniquificationFunctionName: storageUniquificationFunctionName, 95 | resolvedStorageNameAndTypes: resolvedStorageNameAndTypes 96 | ) 97 | for dispatchedVariableDecl in dispatchedVariableDecls { 98 | memberDeclBlock.addMember( 99 | MemberDeclListItemSyntax { item in 100 | item.useDecl( 101 | DeclSyntax( 102 | dispatchedVariableDecl 103 | .withLeadingTrivia(.carriageReturnLineFeeds(2)) 104 | ) 105 | ) 106 | } 107 | ) 108 | } 109 | } 110 | if originalStructDecl.members.members.initializers.isEmpty { 111 | memberDeclBlock.addMember( 112 | MemberDeclListItemSyntax { item in 113 | item.useDecl( 114 | DeclSyntax( 115 | makeStructMemberwiseInitializerDecl( 116 | storageClassName: storageClass.identifier.text, 117 | storageVariableName: storageVariableName, 118 | resolvedStorageNameAndTypes: resolvedStorageNameAndTypes 119 | ).withLeadingTrivia(.carriageReturnLineFeeds(1)) 120 | ) 121 | ) 122 | } 123 | ) 124 | } else { 125 | for eachInitializer in originalStructDecl.members.members.initializers { 126 | memberDeclBlock.addMember( 127 | MemberDeclListItemSyntax { item in 128 | item.useDecl( 129 | DeclSyntax( 130 | makeStructDispatchedInitializerDecl( 131 | originalInitializer: eachInitializer, 132 | storageClassName: storageClass.identifier.text, 133 | storageVariableName: storageVariableName) 134 | ) 135 | ) 136 | } 137 | ) 138 | } 139 | } 140 | for eachMember in originalStructDecl.members.members { 141 | if eachMember.decl.as(VariableDeclSyntax.self)?.isStored == true { 142 | continue 143 | } 144 | if eachMember.decl.is(InitializerDeclSyntax.self) { 145 | continue 146 | } 147 | memberDeclBlock.addMember(eachMember) 148 | } 149 | memberDeclBlock.useRightBrace(.rightBrace) 150 | } 151 | ) 152 | } 153 | 154 | /// Make variable like 155 | /// 156 | /// ``` 157 | /// var foo: Bar { 158 | /// _read { 159 | /// yield storage.foo 160 | /// } 161 | /// _modify { 162 | /// yield &storage.foo 163 | /// } 164 | ///} 165 | /// ``` 166 | /// 167 | /// from 168 | /// 169 | /// ``` 170 | /// var foo: Bar 171 | /// ``` 172 | /// 173 | private func makeStorageDispatchedVariableDecls( 174 | storedPropertyVariableDecl: VariableDeclSyntax, 175 | storageVariableName: String, 176 | storageUniquificationFunctionName: String, 177 | resolvedStorageNameAndTypes: OrderedDictionary 178 | ) -> [VariableDeclSyntax] { 179 | storedPropertyVariableDecl.bindings.map { binding -> VariableDeclSyntax in 180 | VariableDeclSyntax { variableDecl in 181 | let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self)! 182 | let storageName = identifierPattern.identifier.text 183 | variableDecl.useLetOrVarKeyword(.var) 184 | if let attributes = storedPropertyVariableDecl.attributes { 185 | for each in attributes.storageDispatchedVariableAllowedAttributes { 186 | variableDecl.addAttribute(each) 187 | } 188 | } 189 | if let modifiers = storedPropertyVariableDecl.modifiers { 190 | for each in modifiers.storageDispatchedVariableAllowedModifiers { 191 | variableDecl.addModifier(each) 192 | } 193 | } 194 | variableDecl.addBinding( 195 | PatternBindingSyntax { patternBinding in 196 | patternBinding.usePattern(binding.pattern) 197 | patternBinding.useTypeAnnotation( 198 | TypeAnnotationSyntax { typeAnnotation in 199 | typeAnnotation.useColon(.colon) 200 | typeAnnotation.useType(resolvedStorageNameAndTypes[storageName]!) 201 | } 202 | ) 203 | patternBinding.useAccessor( 204 | Syntax( 205 | AccessorBlockSyntax { accessorBlock in 206 | accessorBlock.useLeftBrace(.leftBrace) 207 | accessorBlock.addAccessor( 208 | AccessorDeclSyntax { accessor in 209 | accessor.useAccessorKind(.contextualKeyword("_read")) 210 | accessor.useBody( 211 | CodeBlockSyntax { codeBlock in 212 | codeBlock.useLeftBrace(.leftBrace) 213 | codeBlock.addStatement( 214 | CodeBlockItemSyntax { codeBlockItem in 215 | codeBlockItem.useItem( 216 | Syntax( 217 | YieldStmtSyntax { yieldStmt in 218 | yieldStmt.useYieldKeyword(.yield) 219 | yieldStmt.useYields( 220 | Syntax( 221 | MemberAccessExprSyntax { memberAccess in 222 | memberAccess.useBase( 223 | ExprSyntax( 224 | MemberAccessExprSyntax { memberAccess in 225 | memberAccess.useBase( 226 | ExprSyntax( 227 | IdentifierExprSyntax { identifierExpr in 228 | identifierExpr.useIdentifier(.`self`) 229 | } 230 | ) 231 | ) 232 | memberAccess.useDot(.period) 233 | memberAccess.useName(.identifier(storageVariableName)) 234 | } 235 | ) 236 | ) 237 | memberAccess.useDot(.period) 238 | memberAccess.useName(.identifier(storageName)) 239 | } 240 | ) 241 | ) 242 | } 243 | ) 244 | ) 245 | } 246 | ) 247 | codeBlock.useRightBrace(.rightBrace) 248 | } 249 | ) 250 | } 251 | ) 252 | accessorBlock.addAccessor( 253 | AccessorDeclSyntax { accessor in 254 | accessor.useAccessorKind(.contextualKeyword("_modify")) 255 | accessor.useBody( 256 | CodeBlockSyntax { codeBlock in 257 | codeBlock.useLeftBrace(.leftBrace) 258 | codeBlock.addStatement( 259 | CodeBlockItemSyntax { codeBlockItem in 260 | codeBlockItem.useItem( 261 | Syntax( 262 | FunctionCallExprSyntax { funcCall in 263 | funcCall.useCalledExpression( 264 | ExprSyntax( 265 | MemberAccessExprSyntax { memberAccess in 266 | memberAccess.useBase( 267 | ExprSyntax( 268 | IdentifierExprSyntax { identifierExpr in 269 | identifierExpr.useIdentifier(.`self`) 270 | } 271 | ) 272 | ) 273 | memberAccess.useDot(.period) 274 | memberAccess.useName(.identifier(storageUniquificationFunctionName)) 275 | } 276 | ) 277 | ) 278 | funcCall.useLeftParen(.leftParen) 279 | funcCall.useRightParen(.rightParen) 280 | } 281 | ) 282 | ) 283 | } 284 | ) 285 | codeBlock.addStatement( 286 | CodeBlockItemSyntax { codeBlockItem in 287 | codeBlockItem.useItem( 288 | Syntax( 289 | YieldStmtSyntax { yieldStmt in 290 | yieldStmt.useYieldKeyword(.yield) 291 | yieldStmt.useYields( 292 | Syntax( 293 | InOutExprSyntax { inOutExpr in 294 | inOutExpr.useAmpersand(.prefixAmpersand) 295 | inOutExpr.useExpression( 296 | ExprSyntax( 297 | MemberAccessExprSyntax { memberAccess in 298 | memberAccess.useBase( 299 | ExprSyntax( 300 | MemberAccessExprSyntax { memberAccess in 301 | memberAccess.useBase( 302 | ExprSyntax( 303 | IdentifierExprSyntax { identifierExpr in 304 | identifierExpr.useIdentifier(.`self`) 305 | } 306 | ) 307 | ) 308 | memberAccess.useDot(.period) 309 | memberAccess.useName(.identifier(storageVariableName)) 310 | } 311 | ) 312 | ) 313 | memberAccess.useDot(.period) 314 | memberAccess.useName(.identifier(storageName)) 315 | } 316 | ) 317 | ) 318 | } 319 | ) 320 | ) 321 | } 322 | ) 323 | ) 324 | } 325 | ) 326 | codeBlock.useRightBrace(.rightBrace) 327 | } 328 | ) 329 | } 330 | ) 331 | accessorBlock.useRightBrace(.rightBrace) 332 | } 333 | ) 334 | ) 335 | } 336 | ) 337 | } 338 | } 339 | } 340 | 341 | private func makeStorageUniquificationFunctionDecl( 342 | functionName: String, 343 | storageClassName: String, 344 | storageVariableName: String 345 | ) -> FunctionDeclSyntax { 346 | FunctionDeclSyntax { funcDecl in 347 | funcDecl.useFuncKeyword(.func) 348 | funcDecl.addModifier( 349 | DeclModifierSyntax { declModifierSyntax in 350 | declModifierSyntax.useName(.private) 351 | } 352 | ) 353 | funcDecl.addModifier( 354 | DeclModifierSyntax { declModifierSyntax in 355 | declModifierSyntax.useName(.contextualKeyword("mutating")) 356 | } 357 | ) 358 | funcDecl.useIdentifier(.identifier(functionName)) 359 | funcDecl.useSignature( 360 | FunctionSignatureSyntax { functionSignature in 361 | functionSignature.useInput( 362 | ParameterClauseSyntax { parameterClause in 363 | parameterClause.useLeftParen(.leftParen) 364 | parameterClause.useRightParen(.rightParen) 365 | } 366 | ) 367 | } 368 | ) 369 | funcDecl.useBody( 370 | CodeBlockSyntax { codeBlock in 371 | codeBlock.useLeftBrace(.leftBrace) 372 | codeBlock.addStatement( 373 | CodeBlockItemSyntax { codeBlockItem in 374 | codeBlockItem.useItem( 375 | Syntax( 376 | /* 377 | guard !isKnownUniquelyReferenced(&storage) else { 378 | return 379 | } 380 | */ 381 | GuardStmtSyntax { guardStmt in 382 | guardStmt.useGuardKeyword(.guard) 383 | guardStmt.addCondition( 384 | ConditionElementSyntax { conditionElement in 385 | conditionElement.useCondition( 386 | Syntax( 387 | PrefixOperatorExprSyntax { prefixOperatorExpr in 388 | prefixOperatorExpr.useOperatorToken(.exclamationMark) 389 | prefixOperatorExpr.usePostfixExpression( 390 | ExprSyntax( 391 | FunctionCallExprSyntax { funcCallExpr in 392 | funcCallExpr.useCalledExpression( 393 | ExprSyntax( 394 | IdentifierExprSyntax { identifierExpr in 395 | identifierExpr.useIdentifier(.identifier("isKnownUniquelyReferenced")) 396 | } 397 | ) 398 | ) 399 | funcCallExpr.useLeftParen(.leftParen) 400 | funcCallExpr.addArgument( 401 | TupleExprElementSyntax { tupleExprElet in 402 | tupleExprElet.useExpression( 403 | ExprSyntax( 404 | InOutExprSyntax { inoutExpr in 405 | inoutExpr.useAmpersand(.prefixAmpersand) 406 | inoutExpr.useExpression( 407 | ExprSyntax( 408 | IdentifierExprSyntax { identifierExpr in 409 | identifierExpr.useIdentifier(.identifier(storageVariableName)) 410 | } 411 | ) 412 | ) 413 | } 414 | ) 415 | ) 416 | } 417 | ) 418 | funcCallExpr.useRightParen(.rightParen) 419 | } 420 | ) 421 | ) 422 | } 423 | ) 424 | ) 425 | } 426 | ) 427 | guardStmt.useElseKeyword(.else) 428 | guardStmt.useBody( 429 | CodeBlockSyntax { codeBlock in 430 | codeBlock.useLeftBrace(.leftBrace) 431 | codeBlock.addStatement( 432 | CodeBlockItemSyntax { codeBlockItem in 433 | codeBlockItem.useItem( 434 | Syntax( 435 | ReturnStmtSyntax { returnStmt in 436 | returnStmt.useReturnKeyword(.return) 437 | } 438 | ) 439 | ) 440 | } 441 | ) 442 | codeBlock.useRightBrace(.rightBrace) 443 | } 444 | ) 445 | } 446 | ) 447 | ) 448 | } 449 | ) 450 | /* 451 | self.storage = Storage(storage) 452 | */ 453 | codeBlock.addStatement( 454 | CodeBlockItemSyntax { codeBlockItem in 455 | codeBlockItem.useItem( 456 | Syntax( 457 | SequenceExprSyntax { sequenceExpr in 458 | sequenceExpr.addElement( 459 | ExprSyntax( 460 | MemberAccessExprSyntax { memberAccess in 461 | memberAccess.useBase( 462 | ExprSyntax( 463 | IdentifierExprSyntax { identifier in 464 | identifier.useIdentifier(.`self`) 465 | } 466 | ) 467 | ) 468 | memberAccess.useDot(.period) 469 | memberAccess.useName(.identifier(storageVariableName)) 470 | } 471 | ) 472 | ) 473 | sequenceExpr.addElement( 474 | ExprSyntax( 475 | AssignmentExprSyntax { `assignment` in 476 | `assignment`.useAssignToken(.equal) 477 | } 478 | ) 479 | ) 480 | sequenceExpr.addElement( 481 | ExprSyntax( 482 | FunctionCallExprSyntax { funcCallExpr in 483 | funcCallExpr.useCalledExpression( 484 | ExprSyntax( 485 | IdentifierExprSyntax { identifierExpr in 486 | identifierExpr.useIdentifier(.identifier(storageClassName)) 487 | } 488 | ) 489 | ) 490 | funcCallExpr.useLeftParen(.leftParen) 491 | funcCallExpr.addArgument( 492 | TupleExprElementSyntax { tupleExprElet in 493 | tupleExprElet.useExpression( 494 | ExprSyntax( 495 | IdentifierExprSyntax { identifierExpr in 496 | identifierExpr.useIdentifier(.identifier(storageVariableName)) 497 | } 498 | ) 499 | ) 500 | } 501 | ) 502 | funcCallExpr.useRightParen(.rightParen) 503 | } 504 | ) 505 | ) 506 | } 507 | ) 508 | ) 509 | } 510 | ) 511 | codeBlock.useRightBrace(.rightBrace) 512 | } 513 | ) 514 | } 515 | } 516 | 517 | private func makeStructMemberwiseInitializerDecl( 518 | storageClassName: String, 519 | storageVariableName: String, 520 | resolvedStorageNameAndTypes: OrderedDictionary 521 | ) -> InitializerDeclSyntax { 522 | return InitializerDeclSyntax { initializer in 523 | initializer.useInitKeyword(.`init`) 524 | initializer.useParameters( 525 | ParameterClauseSyntax { parameters in 526 | parameters.useLeftParen(.leftParen) 527 | for (index, (storageName, resolvedStorageType)) in resolvedStorageNameAndTypes.enumerated() { 528 | parameters.addParameter( 529 | FunctionParameterSyntax { parameter in 530 | parameter.useFirstName(.identifier(storageName)) 531 | parameter.useColon(.colon) 532 | parameter.useType(resolvedStorageType) 533 | if index + 1 < resolvedStorageNameAndTypes.count { 534 | parameter.useTrailingComma(.comma) 535 | } 536 | } 537 | ) 538 | } 539 | parameters.useRightParen(.rightParen) 540 | } 541 | ) 542 | initializer.useBody( 543 | CodeBlockSyntax { codeBlock in 544 | codeBlock.useLeftBrace(.leftBrace) 545 | codeBlock.addStatement( 546 | CodeBlockItemSyntax { item in 547 | item.useItem( 548 | Syntax( 549 | SequenceExprSyntax { sequenceExpr in 550 | sequenceExpr.addElement( 551 | ExprSyntax( 552 | MemberAccessExprSyntax { memberAccess in 553 | memberAccess.useBase( 554 | ExprSyntax( 555 | IdentifierExprSyntax { identifier in 556 | identifier.useIdentifier(.`self`) 557 | } 558 | ) 559 | ) 560 | memberAccess.useDot(.period) 561 | memberAccess.useName(.identifier(storageVariableName)) 562 | } 563 | ) 564 | ) 565 | sequenceExpr.addElement( 566 | ExprSyntax( 567 | AssignmentExprSyntax { `assignment` in 568 | `assignment`.useAssignToken(.equal) 569 | } 570 | ) 571 | ) 572 | sequenceExpr.addElement( 573 | ExprSyntax( 574 | FunctionCallExprSyntax { funcCall in 575 | funcCall.useCalledExpression( 576 | ExprSyntax( 577 | IdentifierExprSyntax { identifier in 578 | identifier.useIdentifier(.identifier(storageClassName)) 579 | } 580 | ) 581 | ) 582 | funcCall.useLeftParen(.leftParen) 583 | for (index, (storageName, _)) in resolvedStorageNameAndTypes.enumerated() { 584 | funcCall.addArgument( 585 | TupleExprElementSyntax { tupleExprElet in 586 | tupleExprElet.useLabel(.identifier(storageName)) 587 | tupleExprElet.useColon(.colon) 588 | tupleExprElet.useExpression( 589 | ExprSyntax( 590 | IdentifierExprSyntax { identifierExpr in 591 | identifierExpr.useIdentifier(.identifier(storageName)) 592 | } 593 | ) 594 | ) 595 | if index + 1 < resolvedStorageNameAndTypes.count { 596 | tupleExprElet.useTrailingComma(.comma) 597 | } 598 | } 599 | ) 600 | } 601 | funcCall.useRightParen(.rightParen) 602 | } 603 | ) 604 | ) 605 | } 606 | ) 607 | ) 608 | } 609 | ) 610 | codeBlock.useRightBrace(.rightBrace) 611 | } 612 | ) 613 | } 614 | } 615 | 616 | private func makeStructDispatchedInitializerDecl( 617 | originalInitializer: InitializerDeclSyntax, 618 | storageClassName: String, 619 | storageVariableName: String 620 | ) -> InitializerDeclSyntax { 621 | return originalInitializer.withBody( 622 | CodeBlockSyntax { codeBlock in 623 | codeBlock.useLeftBrace(.leftBrace) 624 | codeBlock.addStatement( 625 | CodeBlockItemSyntax { item in 626 | item.useItem( 627 | Syntax( 628 | SequenceExprSyntax { sequenceExpr in 629 | sequenceExpr.addElement( 630 | ExprSyntax( 631 | MemberAccessExprSyntax { memberAccess in 632 | memberAccess.useBase( 633 | ExprSyntax( 634 | IdentifierExprSyntax { identifier in 635 | identifier.useIdentifier(.`self`) 636 | } 637 | ) 638 | ) 639 | memberAccess.useDot(.period) 640 | memberAccess.useName(.identifier(storageVariableName)) 641 | } 642 | ) 643 | ) 644 | sequenceExpr.addElement( 645 | ExprSyntax( 646 | AssignmentExprSyntax { `assignment` in 647 | `assignment`.useAssignToken(.equal) 648 | } 649 | ) 650 | ) 651 | sequenceExpr.addElement( 652 | ExprSyntax( 653 | FunctionCallExprSyntax { funcCall in 654 | funcCall.useCalledExpression( 655 | ExprSyntax( 656 | IdentifierExprSyntax { identifier in 657 | identifier.useIdentifier(.identifier(storageClassName)) 658 | } 659 | ) 660 | ) 661 | funcCall.useLeftParen(.leftParen) 662 | for (index, parameter) in originalInitializer.parameters.parameterList.enumerated() { 663 | funcCall.addArgument( 664 | TupleExprElementSyntax { tupleExprElet in 665 | tupleExprElet.useLabel(parameter.firstName!) 666 | tupleExprElet.useColon(.colon) 667 | tupleExprElet.useExpression( 668 | ExprSyntax( 669 | IdentifierExprSyntax { identifierExpr in 670 | identifierExpr.useIdentifier((parameter.firstName ?? parameter.secondName)!) 671 | } 672 | ) 673 | ) 674 | if index + 1 < originalInitializer.parameters.parameterList.count { 675 | tupleExprElet.useTrailingComma(.comma) 676 | } 677 | } 678 | ) 679 | } 680 | funcCall.useRightParen(.rightParen) 681 | } 682 | ) 683 | ) 684 | }.withLeadingTrivia(.carriageReturnLineFeeds(1)) 685 | .withTrailingTrivia(.carriageReturnLineFeeds(1)) 686 | ) 687 | ) 688 | } 689 | ) 690 | codeBlock.useRightBrace(.rightBrace) 691 | } 692 | ) 693 | } 694 | 695 | // MARK: - Utilities 696 | 697 | 698 | extension InitializerDeclSyntax { 699 | 700 | func isMemberwiseInitializer(storageNames: S) -> Bool where S.Element == String { 701 | let parameterNames = parameters.parameterList.compactMap { parameter -> String? in 702 | guard let name = (parameter.secondName ?? parameter.firstName) else { 703 | return nil 704 | } 705 | 706 | guard case let .identifier(text) = name.tokenKind else { 707 | return nil 708 | } 709 | 710 | return text 711 | } 712 | return Set(parameterNames) == Set(storageNames) 713 | } 714 | 715 | } 716 | 717 | 718 | extension AttributeListSyntax { 719 | 720 | fileprivate var storageDispatchedVariableAllowedAttributes: [Syntax] { 721 | filter { syntax in 722 | !syntax.is(CustomAttributeSyntax.self) 723 | } 724 | } 725 | 726 | } 727 | 728 | extension ModifierListSyntax { 729 | 730 | fileprivate var storageDispatchedVariableAllowedModifiers: [DeclModifierSyntax] { 731 | filter { syntax in 732 | [.public, .private, .internal, .fileprivate].contains(syntax.name) 733 | } 734 | } 735 | 736 | } 737 | -------------------------------------------------------------------------------- /COWRewriter/Refactorer/MakeStorageClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MakeStorageClass.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/6/22. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftSyntaxBuilder 10 | import Collections 11 | 12 | func makeStorageClass( 13 | structDecl: StructDeclSyntax, 14 | className: String, 15 | resolvedStorageNameAndTypes: OrderedDictionary 16 | ) -> ClassDeclSyntax { 17 | let storedVariables = structDecl.members.members.storedVariables 18 | let initializers = structDecl.members.members.initializers 19 | 20 | let needsCreateMemberwiseInitializer = initializers.reduce(true) { partial, initializer in 21 | partial && !initializer.isMemberwiseInitializer(storageNames: resolvedStorageNameAndTypes.keys) 22 | } 23 | 24 | let memberwiseInitializer: InitializerDeclSyntax? 25 | 26 | if needsCreateMemberwiseInitializer { 27 | memberwiseInitializer = makeStorageClassMemberwiseInitializerDecl( 28 | resolvedStorageNameAndTypes: resolvedStorageNameAndTypes 29 | ).withLeadingTrivia(.carriageReturnLineFeeds(2)) 30 | } else { 31 | memberwiseInitializer = nil 32 | } 33 | 34 | let copyInitializer = makeStorageClassCopyInitializer( 35 | storageClassName: className, 36 | storageNames: resolvedStorageNameAndTypes.keys 37 | ).withLeadingTrivia(.carriageReturnLineFeeds(2)) 38 | 39 | return ClassDeclSyntax { classDecl in 40 | classDecl.addModifier( 41 | DeclModifierSyntax { declModifierSyntax in 42 | declModifierSyntax.useName(.private) 43 | } 44 | ) 45 | classDecl.useClassOrActorKeyword(.class) 46 | classDecl.useIdentifier(.identifier(className)) 47 | classDecl.useMembers( 48 | MemberDeclBlockSyntax { memberDeclBlock in 49 | memberDeclBlock.useLeftBrace(.leftBrace) 50 | for eachStoredProperty in storedVariables { 51 | memberDeclBlock.addMember(MemberDeclListItemSyntax { memberDeclListItem in 52 | memberDeclListItem.useDecl(DeclSyntax(eachStoredProperty)) 53 | }) 54 | } 55 | for eachInitializer in initializers { 56 | memberDeclBlock.addMember(MemberDeclListItemSyntax { memberDeclListItem in 57 | memberDeclListItem.useDecl(DeclSyntax(eachInitializer)) 58 | }) 59 | } 60 | if let memberwiseInitializer = memberwiseInitializer { 61 | memberDeclBlock.addMember( 62 | MemberDeclListItemSyntax { memberDeclListItem in 63 | memberDeclListItem.useDecl(DeclSyntax(memberwiseInitializer)) 64 | } 65 | ) 66 | } 67 | memberDeclBlock.addMember( 68 | MemberDeclListItemSyntax { memberDeclListItem in 69 | memberDeclListItem.useDecl(DeclSyntax(copyInitializer)) 70 | } 71 | ) 72 | memberDeclBlock.useRightBrace(.rightBrace) 73 | } 74 | ) 75 | } 76 | } 77 | 78 | private func makeStorageClassMemberwiseInitializerDecl( 79 | resolvedStorageNameAndTypes: OrderedDictionary 80 | ) -> InitializerDeclSyntax { 81 | return InitializerDeclSyntax { initializer in 82 | initializer.useInitKeyword(.`init`) 83 | initializer.useParameters( 84 | ParameterClauseSyntax { parameters in 85 | parameters.useLeftParen(.leftParen) 86 | for (index, (storageName, resolvedStorageType)) in resolvedStorageNameAndTypes.enumerated() { 87 | parameters.addParameter( 88 | FunctionParameterSyntax { parameter in 89 | parameter.useFirstName(.identifier(storageName)) 90 | parameter.useColon(.colon) 91 | parameter.useType(resolvedStorageType) 92 | if index + 1 < resolvedStorageNameAndTypes.count { 93 | parameter.useTrailingComma(.comma) 94 | } 95 | } 96 | ) 97 | } 98 | parameters.useRightParen(.rightParen) 99 | } 100 | ) 101 | initializer.useBody( 102 | CodeBlockSyntax { codeBlock in 103 | codeBlock.useLeftBrace(.leftBrace) 104 | for (storageName, _) in resolvedStorageNameAndTypes { 105 | codeBlock.addStatement( 106 | CodeBlockItemSyntax { item in 107 | item.useItem( 108 | Syntax( 109 | SequenceExprSyntax { sequenceExpr in 110 | sequenceExpr.addElement( 111 | ExprSyntax( 112 | MemberAccessExprSyntax { memberAccess in 113 | memberAccess.useBase( 114 | ExprSyntax( 115 | IdentifierExprSyntax { identifier in 116 | identifier.useIdentifier(.`self`) 117 | } 118 | ) 119 | ) 120 | memberAccess.useDot(.period) 121 | memberAccess.useName(.identifier(storageName)) 122 | } 123 | ) 124 | ) 125 | sequenceExpr.addElement( 126 | ExprSyntax( 127 | AssignmentExprSyntax { `assignment` in 128 | `assignment`.useAssignToken(.equal) 129 | } 130 | ) 131 | ) 132 | sequenceExpr.addElement( 133 | ExprSyntax( 134 | IdentifierExprSyntax { identifier in 135 | identifier.useIdentifier(.identifier(storageName)) 136 | } 137 | ) 138 | ) 139 | } 140 | ) 141 | ) 142 | } 143 | ) 144 | } 145 | codeBlock.useRightBrace(.rightBrace) 146 | } 147 | ) 148 | } 149 | } 150 | 151 | private func makeStorageClassCopyInitializer( 152 | storageClassName: String, 153 | storageNames: StorageNames 154 | ) -> InitializerDeclSyntax where StorageNames.Element == String { 155 | return InitializerDeclSyntax { initializer in 156 | initializer.useInitKeyword(.`init`) 157 | initializer.useParameters( 158 | ParameterClauseSyntax { parameters in 159 | parameters.useLeftParen(.leftParen) 160 | parameters.addParameter( 161 | FunctionParameterSyntax { parameter in 162 | parameter.useFirstName(.wildcard) 163 | parameter.useSecondName(.identifier("storage")) 164 | parameter.useColon(.colon) 165 | parameter.useType( 166 | TypeSyntax( 167 | SimpleTypeIdentifierSyntax { simpleIdType in 168 | simpleIdType.useName(.identifier(storageClassName)) 169 | } 170 | ) 171 | ) 172 | } 173 | ) 174 | parameters.useRightParen(.rightParen) 175 | } 176 | ) 177 | initializer.useBody( 178 | CodeBlockSyntax { codeBlock in 179 | codeBlock.useLeftBrace(.leftBrace) 180 | for eachStorageName in storageNames { 181 | codeBlock.addStatement( 182 | CodeBlockItemSyntax { item in 183 | item.useItem( 184 | Syntax( 185 | SequenceExprSyntax { sequenceExpr in 186 | sequenceExpr.addElement( 187 | ExprSyntax( 188 | MemberAccessExprSyntax { memberAccess in 189 | memberAccess.useBase( 190 | ExprSyntax( 191 | IdentifierExprSyntax { identifier in 192 | identifier.useIdentifier(.`self`) 193 | } 194 | ) 195 | ) 196 | memberAccess.useDot(.period) 197 | memberAccess.useName(.identifier(eachStorageName)) 198 | } 199 | ) 200 | ) 201 | sequenceExpr.addElement( 202 | ExprSyntax( 203 | AssignmentExprSyntax { `assignment` in 204 | `assignment`.useAssignToken(.equal) 205 | } 206 | ) 207 | ) 208 | sequenceExpr.addElement( 209 | ExprSyntax( 210 | MemberAccessExprSyntax { memberAccess in 211 | memberAccess.useBase( 212 | ExprSyntax( 213 | IdentifierExprSyntax { identifier in 214 | identifier.useIdentifier(.identifier("storage")) 215 | } 216 | ) 217 | ) 218 | memberAccess.useDot(.period) 219 | memberAccess.useName(.identifier(eachStorageName)) 220 | } 221 | ) 222 | ) 223 | } 224 | ).withLeadingTrivia(.carriageReturnLineFeeds(1)) 225 | .withTrailingTrivia(.carriageReturnLineFeeds(1)) 226 | ) 227 | } 228 | ) 229 | } 230 | codeBlock.useRightBrace(.rightBrace) 231 | } 232 | ) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /COWRewriter/Refactorer/Refactorer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Refactorer.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/25/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | import SwiftSyntaxParser 11 | 12 | struct RefactorableDecl: Hashable { 13 | 14 | let treeID: UInt 15 | 16 | let identifier: String 17 | 18 | let sourceRange: SourceRange 19 | 20 | let namingSuggestions: [NamingKey : String] 21 | 22 | let unresolvedSemantics: [UnresolvedSemantics] 23 | 24 | } 25 | 26 | enum UnresolvedSemantics: Hashable { 27 | 28 | case name(NamingIssue) 29 | 30 | case typeAnnotation(TypeAnnotationIssue) 31 | 32 | @inlinable 33 | var treeID: UInt { 34 | switch self { 35 | case let .name(issue): return issue.treeID 36 | case let .typeAnnotation(issue): return issue.treeID 37 | } 38 | } 39 | 40 | @inlinable 41 | var sourceRange: SourceRange { 42 | switch self { 43 | case let .name(issue): return issue.sourceRange 44 | case let .typeAnnotation(issue): return issue.sourceRange 45 | } 46 | } 47 | 48 | /// The identifier for the error type in the context. 49 | @inlinable 50 | var id: UInt { 51 | switch self { 52 | case let .name(issue): return issue.id 53 | case let .typeAnnotation(issue): return issue.id 54 | } 55 | } 56 | 57 | struct NamingIssue: Hashable { 58 | 59 | typealias Key = NamingKey 60 | 61 | let treeID: UInt 62 | 63 | let sourceRange: SourceRange 64 | 65 | /// The identifier for the error type in the context. 66 | let id: UInt 67 | 68 | let key: NamingKey 69 | 70 | let suggestedName: String? 71 | 72 | } 73 | 74 | /// Pattern bindings that cannot infer its type. 75 | /// 76 | /// - Note: COW refactoring based on pattern binding's type. 77 | /// 78 | struct TypeAnnotationIssue: Hashable { 79 | 80 | let treeID: UInt 81 | 82 | let sourceRange: SourceRange 83 | 84 | /// The identifier for the error type in the context. 85 | let id: UInt 86 | 87 | let letOrVar: String 88 | 89 | let identifier: String 90 | 91 | let maybeType: TypeSyntax? 92 | 93 | } 94 | 95 | 96 | } 97 | 98 | struct NamingKey: RawRepresentable, Hashable { 99 | 100 | typealias RawValue = String 101 | 102 | var rawValue: RawValue 103 | 104 | init(rawValue: String) { 105 | self.rawValue = rawValue 106 | } 107 | 108 | static let storageClassName = NamingKey(rawValue: "Storage Class Name") 109 | 110 | static let storageVariableName = NamingKey(rawValue: "Storage Variable Name") 111 | 112 | static let storageUniquificationFunctionName = NamingKey(rawValue: "Storage Uniquification Function Name") 113 | 114 | } 115 | 116 | struct RefactorRequest: Equatable { 117 | 118 | let decl: RefactorableDecl 119 | 120 | let storageClassName: String 121 | 122 | let storageVariableName: String 123 | 124 | let storageUniquificationFunctionName: String 125 | 126 | let typedefs: [String : TypeSyntax] 127 | 128 | } 129 | 130 | final class Refactorer: Equatable { 131 | 132 | private let target: Target 133 | 134 | private let file: String? 135 | 136 | private let tree: SourceFileSyntax 137 | 138 | private let slc: SourceLocationConverter 139 | 140 | private let treeID: UInt 141 | 142 | @inlinable 143 | var refactorableDecls: [RefactorableDecl] { 144 | get async { 145 | if let decls = _refactorableDecls_ { 146 | return decls 147 | } 148 | 149 | class Context: SemaInputting, SemaOutputting { 150 | 151 | let refactorer: Refactorer 152 | 153 | var tree: SourceFileSyntax 154 | 155 | let treeID: UInt 156 | 157 | var refactorableDecls: [RefactorableDecl] 158 | 159 | @inline(__always) 160 | var slc: SourceLocationConverter { 161 | _read { 162 | yield refactorer.slc 163 | } 164 | } 165 | 166 | @inline(__always) 167 | init(refactorer: Refactorer) { 168 | self.refactorer = refactorer 169 | self.tree = refactorer.tree 170 | self.treeID = refactorer.treeID 171 | self.refactorableDecls = [] 172 | } 173 | 174 | } 175 | 176 | let context = Context(refactorer: self) 177 | let sema = Sema(target: target, input: context, output: context) 178 | sema.performIfNeeded() 179 | _refactorableDecls_ = context.refactorableDecls 180 | return context.refactorableDecls 181 | } 182 | } 183 | 184 | @inlinable 185 | func refactor(_ requests: [RefactorRequest]) async -> SourceFileSyntax { 186 | 187 | class Context: COWRewriterInputContext { 188 | 189 | let file: String? 190 | 191 | var tree: SourceFileSyntax 192 | 193 | let slc: SourceLocationConverter 194 | 195 | let refactorableDecls: [RefactorableDecl] 196 | 197 | @inline(__always) 198 | init(refactorer: Refactorer) async { 199 | self.file = refactorer.file 200 | self.tree = refactorer.tree 201 | self.slc = refactorer.slc 202 | self.refactorableDecls = await refactorer.refactorableDecls 203 | } 204 | 205 | func rewriter(_ sender: COWRewriter, shouldRewriteDeclFrom startLocation: SourceLocation, to endLocation: SourceLocation) -> Bool { 206 | refactorableDecls.contains { each in 207 | each.sourceRange.start == startLocation && each.sourceRange.end == endLocation 208 | } 209 | } 210 | 211 | } 212 | 213 | let context = await Context(refactorer: self) 214 | let rewriter = COWRewriter(input: context) 215 | return rewriter.execute(requests: requests) 216 | } 217 | 218 | @inlinable 219 | init(target: Target, file: String, tree: SourceFileSyntax) { 220 | self.target = target 221 | self.file = file 222 | self.tree = tree 223 | self.slc = SourceLocationConverter(file: file, tree: tree) 224 | self._refactorableDecls_ = nil 225 | self.treeID = UInt.random(in: .min...(.max)) 226 | } 227 | 228 | enum InitializationError: Error, CustomStringConvertible { 229 | 230 | case notFileUrl(URL) 231 | 232 | case parserError(URL, Error) 233 | 234 | var description: String { 235 | switch self { 236 | case let .notFileUrl(url): 237 | return "\(url) is not a file URL." 238 | case let .parserError(url, error): 239 | return "Error happened during parsing Swift source at \(url): \(error.localizedDescription)" 240 | } 241 | } 242 | 243 | } 244 | 245 | convenience init(url: URL) throws { 246 | guard url.isFileURL else { 247 | throw InitializationError.notFileUrl(url) 248 | } 249 | 250 | do { 251 | let tree = try SyntaxParser.parse(url) 252 | let file = url.absoluteURL.path 253 | self.init(target: .host, file: file, tree: tree) 254 | } catch let error { 255 | throw InitializationError.parserError(url, error) 256 | } 257 | } 258 | 259 | static func == (lhs: Refactorer, rhs: Refactorer) -> Bool { 260 | return lhs === rhs 261 | } 262 | 263 | // MARK: Backwarded Properties 264 | 265 | private var _refactorableDecls_: [RefactorableDecl]? 266 | 267 | } 268 | 269 | extension Refactorer { 270 | 271 | @inlinable 272 | convenience init?(target: Target, source: String) async { 273 | guard let tree = try? SyntaxParser.parse(source: source) else { 274 | return nil 275 | } 276 | self.init(target: target, file: "SOURCE_IN_MEMORY", tree: tree) 277 | } 278 | 279 | @inlinable 280 | convenience init?(target: Target, url: URL) async { 281 | guard let tree = try? SyntaxParser.parse(url) else { 282 | return nil 283 | } 284 | self.init(target: target, file: url.path, tree: tree) 285 | } 286 | 287 | @inlinable 288 | convenience init?(target: Target, path: String) async { 289 | await self.init(target: target, url: URL(fileURLWithPath: path)) 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /COWRewriter/Refactorer/Sema.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sema.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/26/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | import SwiftSyntaxBuilder 11 | import SwiftUI 12 | 13 | enum Target { 14 | 15 | case of32Bit 16 | 17 | case of64Bit 18 | 19 | static var host: Target { 20 | #if arch(x86_64) || arch(arm64) 21 | return .of64Bit 22 | #else 23 | return .of32Bit 24 | #endif 25 | } 26 | 27 | } 28 | 29 | protocol SemaInputting: AnyObject { 30 | 31 | var tree: SourceFileSyntax { get } 32 | 33 | var treeID: UInt { get } 34 | 35 | var slc: SourceLocationConverter { get } 36 | 37 | } 38 | 39 | 40 | protocol SemaOutputting: AnyObject { 41 | 42 | var tree: SourceFileSyntax { get set } 43 | 44 | var refactorableDecls: [RefactorableDecl] { get set } 45 | 46 | } 47 | 48 | /// A simple semantic analyzing process 49 | /// 50 | /// In this process, we do: 51 | /// - Inferring missing types. 52 | /// - Collect refactorable decls. 53 | /// 54 | class Sema { 55 | 56 | let target: Target 57 | 58 | unowned let input: SemaInputting 59 | 60 | unowned let output: SemaOutputting 61 | 62 | private var hasPerformed: Bool 63 | 64 | init(target: Target, input: SemaInputting, output: SemaOutputting) { 65 | self.target = target 66 | self.input = input 67 | self.output = output 68 | self.hasPerformed = false 69 | } 70 | 71 | func performIfNeeded() { 72 | if !hasPerformed { 73 | perform() 74 | hasPerformed = false 75 | } 76 | } 77 | 78 | @inline(__always) 79 | private func perform() { 80 | /* 81 | let typeChecker = TypeChecker( 82 | target: target, 83 | tree: input.tree, 84 | slc: input.slc 85 | ) 86 | */ 87 | let typeCheckedTree = input.tree // FIXME: typeChecker.check() 88 | let detector = SimpleRefactorableDeclsDetector( 89 | treeID: input.treeID, 90 | tree: typeCheckedTree, 91 | slc: input.slc 92 | ) 93 | output.tree = typeCheckedTree 94 | output.refactorableDecls = detector.detect() 95 | } 96 | 97 | } 98 | 99 | 100 | private class LiteralResolvingContext { 101 | 102 | let target: Target 103 | 104 | init(target: Target) { 105 | self.target = target 106 | } 107 | 108 | } 109 | 110 | // FIXME: Crashes when type checking XML files with .swift extension. 111 | 112 | /// Currently only support type inferring for limited literal initializing of 113 | /// variable bindings. 114 | private class TypeChecker: SyntaxRewriter { 115 | 116 | class Scope { 117 | 118 | enum LiteralType { 119 | 120 | case string 121 | 122 | case integer 123 | 124 | case boolean 125 | 126 | case float 127 | 128 | func resolvedName(context: LiteralResolvingContext) -> String { 129 | switch self { 130 | case .string: return "String" 131 | case .integer: return "Int" 132 | case .boolean: return "Bool" 133 | case .float: 134 | switch context.target { 135 | case .of32Bit: return "Float" 136 | case .of64Bit: return "Double" 137 | } 138 | } 139 | } 140 | 141 | } 142 | 143 | var isInInitializerClause: Bool 144 | 145 | var initialzerLiteral: LiteralType? 146 | 147 | init() { 148 | isInInitializerClause = false 149 | initialzerLiteral = nil 150 | } 151 | 152 | } 153 | 154 | var scopes: [Scope] 155 | 156 | let tree: Syntax 157 | 158 | let slc: SourceLocationConverter 159 | 160 | private var result: Syntax? 161 | 162 | let target: Target 163 | 164 | private let context: LiteralResolvingContext 165 | 166 | init(target: Target, tree: Syntax, slc: SourceLocationConverter) { 167 | self.target = target 168 | self.scopes = [] 169 | self.tree = tree 170 | self.slc = slc 171 | self.context = LiteralResolvingContext(target: target) 172 | } 173 | 174 | func check() -> Syntax { 175 | if let result = result { 176 | return result 177 | } 178 | var inputTree = tree 179 | var outputTree: Syntax 180 | repeat { 181 | outputTree = visit(inputTree) 182 | inputTree = outputTree 183 | } while inputTree != outputTree 184 | result = outputTree 185 | return outputTree 186 | } 187 | 188 | override func visit(_ node: InitializerClauseSyntax) -> Syntax { 189 | scopes.last!.isInInitializerClause = true 190 | let result = super.visit(node) 191 | scopes.last!.isInInitializerClause = false 192 | return result 193 | } 194 | 195 | override func visit(_ node: PatternBindingSyntax) -> Syntax { 196 | scopes.append(Scope()) 197 | let result = super.visit(node) 198 | let popped = scopes.removeLast() 199 | if let literal = popped.initialzerLiteral, node.typeAnnotation == nil { 200 | var typeAnnotatedNode = node 201 | typeAnnotatedNode.typeAnnotation = TypeAnnotationSyntax { builder in 202 | builder.useColon(.colon) 203 | builder.useType(TypeSyntax(SimpleTypeIdentifierSyntax { id in 204 | id.useName(.identifier(literal.resolvedName(context: context))) 205 | })) 206 | } 207 | return Syntax(typeAnnotatedNode) 208 | } 209 | return result 210 | } 211 | 212 | override func visit(_ node: OptionalBindingConditionSyntax) -> Syntax { 213 | scopes.append(Scope()) 214 | let result = super.visit(node) 215 | let popped = scopes.removeLast() 216 | if let literal = popped.initialzerLiteral, node.typeAnnotation == nil { 217 | var typeAnnotatedNode = node 218 | typeAnnotatedNode.typeAnnotation = TypeAnnotationSyntax { builder in 219 | builder.useColon(.colon) 220 | builder.useType(TypeSyntax(SimpleTypeIdentifierSyntax { id in 221 | id.useName(.identifier(literal.resolvedName(context: context))) 222 | })) 223 | } 224 | return Syntax(typeAnnotatedNode) 225 | } 226 | return result 227 | } 228 | 229 | override func visit(_ node: FloatLiteralExprSyntax) -> ExprSyntax { 230 | scopes.last!.initialzerLiteral = .float 231 | return super.visit(node) 232 | } 233 | 234 | override func visit(_ node: StringLiteralExprSyntax) -> ExprSyntax { 235 | scopes.last!.initialzerLiteral = .string 236 | return super.visit(node) 237 | } 238 | 239 | override func visit(_ node: IntegerLiteralExprSyntax) -> ExprSyntax { 240 | scopes.last!.initialzerLiteral = .integer 241 | return super.visit(node) 242 | } 243 | 244 | override func visit(_ node: BooleanLiteralExprSyntax) -> ExprSyntax { 245 | scopes.last!.initialzerLiteral = .boolean 246 | return super.visit(node) 247 | } 248 | 249 | } 250 | 251 | 252 | private class SimpleRefactorableDeclsDetector: SyntaxVisitor { 253 | 254 | struct UntyppedBinding { 255 | 256 | let letOrVar: String 257 | 258 | let identifier: String 259 | 260 | let sourceRange: SourceRange 261 | 262 | } 263 | 264 | let treeID: UInt 265 | 266 | let tree: SourceFileSyntax 267 | 268 | let slc: SourceLocationConverter 269 | 270 | private var hasDetected: Bool 271 | 272 | private var structDeclSyntaxs: [StructDeclSyntax] 273 | 274 | private var refactorableDecls: [RefactorableDecl] 275 | 276 | init(treeID: UInt, tree: SourceFileSyntax, slc: SourceLocationConverter) { 277 | self.treeID = treeID 278 | self.tree = tree 279 | self.slc = slc 280 | self.hasDetected = false 281 | self.structDeclSyntaxs = [] 282 | self.refactorableDecls = [] 283 | } 284 | 285 | func detect() -> [RefactorableDecl] { 286 | if hasDetected { 287 | return refactorableDecls 288 | } 289 | walk(tree) 290 | update(structDeclSyntaxs) 291 | hasDetected = true 292 | return refactorableDecls 293 | } 294 | 295 | override func visitPost(_ node: StructDeclSyntax) { 296 | super.visitPost(node) 297 | structDeclSyntaxs.append(node) 298 | } 299 | 300 | private struct SuggestedStorageNamings { 301 | 302 | var storageClassName: String 303 | 304 | var storageVariableName: String 305 | 306 | var storageUniquificationFunctionName: String 307 | 308 | init() { 309 | storageClassName = "Storage" 310 | storageVariableName = "storage" 311 | storageUniquificationFunctionName = "makeUniqueStorageIfNeeded" 312 | } 313 | 314 | } 315 | 316 | private func makeStorageRelatedUnresolvedSemanticsIssues( 317 | for structDecl: StructDeclSyntax 318 | ) -> ([UnresolvedSemantics], [NamingKey : String]) { 319 | typealias Names = ( 320 | instanceVariableNames: Set, 321 | instanceFunctionNames: Set, 322 | subTypeNames: Set 323 | ) 324 | let ( 325 | instanceVariableNames, 326 | instanceFunctionNames, 327 | subTypeNames 328 | ) = structDecl.members.members.reduce(([], [], [])) { partial, each -> Names in 329 | var (instanceVariableNames, instanceFunctionNames,subTypeNames) = partial 330 | if let varDecl = each.decl.as(VariableDeclSyntax.self) { 331 | let isStatic = varDecl.modifiers?.contains(where: { $0.name == .static }) == true 332 | for binding in varDecl.bindings { 333 | guard let idPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { 334 | return (instanceVariableNames, instanceFunctionNames, subTypeNames) 335 | } 336 | if !isStatic { 337 | instanceVariableNames.insert(idPattern.identifier.withoutTrivia().text) 338 | } 339 | } 340 | } else if let funcDecl = each.decl.as(FunctionDeclSyntax.self) { 341 | if !(funcDecl.modifiers?.contains(where: { $0.name == .static }) == true) { 342 | instanceFunctionNames.insert(funcDecl.identifier.withoutTrivia().text) 343 | } 344 | } else if let structDecl = each.decl.as(StructDeclSyntax.self) { 345 | subTypeNames.insert(structDecl.identifier.withoutTrivia().text) 346 | } else if let enumDecl = each.decl.as(EnumDeclSyntax.self) { 347 | subTypeNames.insert(enumDecl.identifier.withoutTrivia().text) 348 | } else if let classDecl = each.decl.as(ClassDeclSyntax.self) { 349 | subTypeNames.insert(classDecl.identifier.withoutTrivia().text) 350 | } 351 | return (instanceVariableNames, instanceFunctionNames, subTypeNames) 352 | } 353 | 354 | func resolveName(_ name: String, with existedNames: Set) 355 | -> ( 356 | resolvedName: String, 357 | conflictCount: Int 358 | ) { 359 | var conflistCount = 0 360 | var resolvedName = name 361 | while existedNames.contains(resolvedName) { 362 | // If there is `Storage`, then try `Storage2` 363 | resolvedName = "\(name)\(conflistCount + 2)" 364 | conflistCount += 1 365 | } 366 | return (resolvedName, conflistCount) 367 | } 368 | 369 | var suggestedNamings = [NamingKey : String]() 370 | suggestedNamings[.storageClassName] = "Storage" 371 | suggestedNamings[.storageVariableName] = "storage" 372 | suggestedNamings[.storageUniquificationFunctionName] = "makeUniqueStorageIfNeeded" 373 | 374 | typealias Item = (key: NamingKey, existedNames: Set) 375 | 376 | let items: [Item] = [ 377 | (.storageClassName, subTypeNames), 378 | (.storageVariableName, instanceVariableNames), 379 | (.storageUniquificationFunctionName, instanceFunctionNames), 380 | ] 381 | 382 | let sourceRange = structDecl.sourceRange(converter: slc) 383 | 384 | let unresolvedSemantics = items.compactMap { 385 | (key, existednames) -> UnresolvedSemantics? in 386 | let name = suggestedNamings[key]! 387 | let (resolvedName, conflictsCount) = resolveName(name, with: existednames) 388 | if conflictsCount == 0 { 389 | return nil 390 | } 391 | suggestedNamings[key] = resolvedName 392 | return .name( 393 | .init( 394 | treeID: treeID, 395 | sourceRange: sourceRange, 396 | id: UInt(sourceRange: sourceRange, key: key), 397 | key: key, 398 | suggestedName: resolvedName 399 | ) 400 | ) 401 | } 402 | 403 | return (unresolvedSemantics, suggestedNamings) 404 | } 405 | 406 | private func makeTypeAnnotationIssues( 407 | for structDeclSyntax: StructDeclSyntax 408 | ) -> [UnresolvedSemantics] { 409 | var untyppedBindings = [UnresolvedSemantics]() 410 | for eachMember in structDeclSyntax.members.members { 411 | if let varDecl = eachMember.decl.as(VariableDeclSyntax.self) { 412 | for binding in varDecl.bindings { 413 | guard let idPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { 414 | continue 415 | } 416 | if binding.typeAnnotation == nil { 417 | let sourceRange = binding.sourceRange(converter: slc) 418 | untyppedBindings.append( 419 | .typeAnnotation( 420 | UnresolvedSemantics.TypeAnnotationIssue( 421 | treeID: treeID, 422 | sourceRange: sourceRange, 423 | id: UInt(sourceRange: sourceRange), 424 | letOrVar: varDecl.letOrVarKeyword == .let ? "let" : "var", 425 | identifier: idPattern.identifier.withoutTrivia().text, 426 | maybeType: nil 427 | ) 428 | ) 429 | ) 430 | } 431 | } 432 | } 433 | } 434 | return untyppedBindings 435 | } 436 | 437 | private func update(_ syntaxes: [StructDeclSyntax]) { 438 | guard syntaxes.count > 0 else { 439 | return 440 | } 441 | 442 | self.refactorableDecls = syntaxes.compactMap { decl in 443 | 444 | guard decl.members.members.storedVariables.count > 0 else { 445 | return nil 446 | } 447 | 448 | let (storageIssues, storageNamingSuggestions) = makeStorageRelatedUnresolvedSemanticsIssues(for: decl) 449 | 450 | let typeIssues = makeTypeAnnotationIssues(for: decl) 451 | 452 | return RefactorableDecl( 453 | treeID: treeID, 454 | identifier: decl.identifier.withoutTrivia().text, 455 | sourceRange: decl.sourceRange(converter: slc), 456 | namingSuggestions: storageNamingSuggestions, 457 | unresolvedSemantics: typeIssues + storageIssues 458 | ) 459 | } 460 | } 461 | 462 | } 463 | 464 | extension VariableDeclSyntax { 465 | 466 | fileprivate var usesLetKeyword: Bool { 467 | if letOrVarKeyword.tokenKind == .letKeyword { 468 | return true 469 | } 470 | return false 471 | } 472 | 473 | } 474 | 475 | 476 | extension PatternBindingSyntax { 477 | 478 | fileprivate var hasStorage: Bool { 479 | accessor == nil 480 | } 481 | 482 | } 483 | 484 | 485 | extension UInt { 486 | 487 | @inline(__always) 488 | fileprivate init(sourceRange: SourceRange, key: Key?) { 489 | self.init( 490 | startLocation: sourceRange.start, 491 | endLocation: sourceRange.end, 492 | key: key 493 | ) 494 | } 495 | 496 | @inline(__always) 497 | fileprivate init( 498 | startLocation: SourceLocation, 499 | endLocation: SourceLocation, 500 | key: Key? 501 | ) { 502 | self.init( 503 | startOffset: startLocation.offset, 504 | endOffset: endLocation.offset, 505 | key: key 506 | ) 507 | } 508 | 509 | @inline(__always) 510 | fileprivate init(startOffset: Int, endOffset: Int, key: Key?) { 511 | var hasher = Hasher() 512 | hasher.combine(startOffset) 513 | hasher.combine(endOffset) 514 | if let key = key { 515 | hasher.combine(key) 516 | } 517 | self = UInt(bitPattern: hasher.finalize()) 518 | } 519 | 520 | @inline(__always) 521 | fileprivate init(sourceRange: SourceRange) { 522 | self.init(startLocation: sourceRange.start, endLocation: sourceRange.end) 523 | } 524 | 525 | @inline(__always) 526 | fileprivate init(startLocation: SourceLocation, endLocation: SourceLocation) { 527 | self.init(startOffset: startLocation.offset, endOffset: endLocation.offset) 528 | } 529 | 530 | @inline(__always) 531 | fileprivate init(startOffset: Int, endOffset: Int) { 532 | var hasher = Hasher() 533 | hasher.combine(startOffset) 534 | hasher.combine(endOffset) 535 | self = UInt(bitPattern: hasher.finalize()) 536 | } 537 | 538 | } 539 | -------------------------------------------------------------------------------- /COWRewriter/Session/RefactorCandidate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefactorCandidate.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/5/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RefactorCandidate: Hashable, Identifiable { 11 | 12 | let content: RefactorableDecl 13 | 14 | let id: UUID 15 | 16 | var isSelected: Bool 17 | 18 | init(content: RefactorableDecl) { 19 | self.content = content 20 | self.id = UUID() 21 | self.isSelected = false 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /COWRewriter/Session/RefactorRequestConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefactorRequestConfig.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/5/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | import SwiftSyntaxParser 11 | 12 | struct RefactorRequestConfig: Equatable, Identifiable { 13 | 14 | let id: UUID 15 | 16 | let decl: RefactorableDecl 17 | 18 | let declName: String 19 | 20 | var unresolvedSemanticsItems: [UnresolvedSemanticsItem] 21 | 22 | init(candidate: RefactorCandidate) { 23 | id = candidate.id 24 | let content = candidate.content 25 | decl = content 26 | declName = content.identifier 27 | unresolvedSemanticsItems = content.unresolvedSemantics.map(UnresolvedSemanticsItem.init) 28 | } 29 | 30 | var request: RefactorRequest { 31 | func makeTypeSyntax(_ userType: String?) -> TypeSyntax? { 32 | guard let userType = userType else { 33 | return nil 34 | } 35 | guard let tree = try? SyntaxParser.parse(source: userType) else { 36 | return nil 37 | } 38 | return tree.statements.first?.item.as(TypeSyntax.self) 39 | } 40 | func makeTypedef(_ item: UnresolvedSemanticsItem) -> (String, TypeSyntax)? { 41 | guard case let .typeAnnotation(issue) = item, 42 | let type = makeTypeSyntax(issue.userType) ?? issue.suggestedType else { 43 | return nil 44 | } 45 | return (issue.name, type) 46 | } 47 | func select( 48 | key: UnresolvedSemantics.NamingIssue.Key, 49 | from items: Items 50 | ) -> String? where Items.Element == UnresolvedSemanticsItem { 51 | for item in items { 52 | guard case let .name(issue) = item, issue.key == key else { 53 | continue 54 | } 55 | return issue.userSpecifiedName.isEmpty 56 | ? (issue.suggestedName ?? decl.namingSuggestions[key] ) 57 | : issue.userSpecifiedName 58 | } 59 | return decl.namingSuggestions[key] 60 | } 61 | return RefactorRequest( 62 | decl: decl, 63 | storageClassName: select(key: .storageClassName, from: unresolvedSemanticsItems) ?? "", 64 | storageVariableName: select(key: .storageVariableName, from: unresolvedSemanticsItems) ?? "", 65 | storageUniquificationFunctionName: select(key: .storageUniquificationFunctionName, from: unresolvedSemanticsItems) ?? "", 66 | typedefs: Dictionary(uniqueKeysWithValues: unresolvedSemanticsItems.compactMap(makeTypedef)) 67 | ) 68 | } 69 | 70 | enum UnresolvedSemanticsItem: Equatable, Identifiable { 71 | 72 | case name(NamingIssue) 73 | 74 | case typeAnnotation(TypeAnnotationIssue) 75 | 76 | @inline(__always) 77 | var id: UInt { 78 | switch self { 79 | case let .name(issue): return issue.id 80 | case let .typeAnnotation(issue): return issue.id 81 | } 82 | } 83 | 84 | init(_ semantics: UnresolvedSemantics) { 85 | switch semantics { 86 | case let .name(issue): 87 | self = .name(NamingIssue(issue)) 88 | case let .typeAnnotation(issue): 89 | self = .typeAnnotation(TypeAnnotationIssue(issue)) 90 | } 91 | } 92 | 93 | struct TypeAnnotationIssue: Equatable, Identifiable { 94 | 95 | let id: UInt 96 | 97 | let letOrVar: String 98 | 99 | let name: String 100 | 101 | let suggestedType: TypeSyntax? 102 | 103 | var userType: String 104 | 105 | init(_ issue: UnresolvedSemantics.TypeAnnotationIssue) { 106 | self.id = issue.id 107 | self.letOrVar = issue.letOrVar 108 | self.name = issue.identifier 109 | self.suggestedType = issue.maybeType 110 | self.userType = "" 111 | } 112 | 113 | } 114 | 115 | struct NamingIssue: Equatable { 116 | 117 | let id: UInt 118 | 119 | let key: Key 120 | 121 | let suggestedName: String? 122 | 123 | var userSpecifiedName: String 124 | 125 | init(_ issue: UnresolvedSemantics.NamingIssue) { 126 | self.id = issue.id 127 | self.key = issue.key 128 | self.suggestedName = issue.suggestedName 129 | self.userSpecifiedName = "" 130 | } 131 | 132 | typealias Key = UnresolvedSemantics.NamingIssue.Key 133 | 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /COWRewriter/Session/Session.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Session.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | import AppKit 11 | import SwiftUI 12 | 13 | 14 | class Session: ObservableObject, BottomToolbarActions { 15 | 16 | let fileUrl: URL 17 | 18 | @Published 19 | var candidates: [RefactorCandidate] 20 | 21 | @Published 22 | var selectedCandidates: Set 23 | 24 | @Published 25 | var refactorRequestConfigs: [RefactorRequestConfig] 26 | 27 | @Published 28 | var contentsPreview: String 29 | 30 | @Published 31 | var refactorRequests: [RefactorRequest] 32 | 33 | @Published 34 | var printerConfigs: PrinterConfigs 35 | 36 | private let refactorer: Refactorer 37 | 38 | private let printer: Printer 39 | 40 | private var disposables: Set 41 | 42 | init(fileUrl: URL) throws { 43 | self.fileUrl = fileUrl 44 | self.candidates = [] 45 | self.selectedCandidates = [] 46 | self.refactorRequestConfigs = [] 47 | self.refactorRequests = [] 48 | self.contentsPreview = "" 49 | self.refactorer = try Refactorer(url: fileUrl) 50 | let printer = Printer() 51 | self.printerConfigs = printer.configs 52 | self.printer = printer 53 | self.disposables = [] 54 | setUp() 55 | } 56 | 57 | private func setUp() { 58 | $candidates 59 | .debounce(for: 0.1, scheduler: DispatchQueue.main) 60 | .receive(on: DispatchQueue.main) 61 | .map({Set($0.filter(\.isSelected).map(\.id))}) 62 | .assign(to: &$selectedCandidates) 63 | 64 | $selectedCandidates 65 | .combineLatest($refactorRequestConfigs) 66 | .receive(on: DispatchQueue.main) 67 | .map { (selectedCandidates, configs) in 68 | configs 69 | .filter({selectedCandidates.contains($0.id)}) 70 | .map(\.request) 71 | } 72 | .assign(to: &$refactorRequests) 73 | 74 | $refactorRequests.combineLatest($printerConfigs) 75 | .receive(on: DispatchQueue.main) 76 | .sink(receiveValue: updateRefactorResults) 77 | .store(in: &disposables) 78 | 79 | $printerConfigs 80 | .sink { configs in 81 | self.printer.configs = configs 82 | } 83 | .store(in: &disposables) 84 | 85 | Task { 86 | let refactorableDecls = await refactorer.refactorableDecls 87 | await updateCandidates(refactorableDecls.map(RefactorCandidate.init)) 88 | } 89 | 90 | updateRefactorResults(refactorRequests, printerConfigs) 91 | } 92 | 93 | private func updateRefactorResults( 94 | _ requests: [RefactorRequest], 95 | _ config: PrinterConfigs 96 | ) { 97 | Task { 98 | let contents = printer.print( 99 | syntax: await refactorer.refactor(refactorRequests), 100 | url: fileUrl 101 | ) 102 | await updateContentsPreview(contents) 103 | } 104 | } 105 | 106 | @Sendable 107 | @MainActor 108 | private func updateCandidates(_ candidates: [RefactorCandidate]) { 109 | self.candidates = candidates 110 | self.refactorRequestConfigs = candidates.map(RefactorRequestConfig.init) 111 | } 112 | 113 | @Sendable 114 | @MainActor 115 | private func updateContentsPreview(_ contents: String) { 116 | self.contentsPreview = contents 117 | } 118 | 119 | func selectAll() { 120 | for index in candidates.indices { 121 | candidates[index].isSelected = true 122 | } 123 | } 124 | 125 | func deselectAll() { 126 | for index in candidates.indices { 127 | candidates[index].isSelected = false 128 | } 129 | } 130 | 131 | func copy() async { 132 | let contents = await refactorer.refactor(refactorRequests) 133 | NSPasteboard.general.clearContents() 134 | NSPasteboard.general.setString( 135 | printer.print(syntax: contents, url: fileUrl), 136 | forType: .string 137 | ) 138 | } 139 | 140 | func saveAs(url: URL) async throws { 141 | let syntax = await refactorer.refactor(refactorRequests) 142 | let text = printer.print(syntax: syntax, url: fileUrl) 143 | try text.data(using: .utf8)?.write(to: url) 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /COWRewriter/Session/SessionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionManager.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class SessionManager: ObservableObject { 12 | 13 | @Published 14 | var selectedFileURL: URL? 15 | 16 | @Published 17 | var currentSession: Session? 18 | 19 | @Published 20 | var importErrorMessage: String? 21 | 22 | init() { 23 | $selectedFileURL 24 | .receive(on: DispatchQueue.main).map(makeSession) 25 | .assign(to: &$currentSession) 26 | } 27 | 28 | private func makeSession(url: URL?) -> Session? { 29 | guard let url = url else { 30 | return nil 31 | } 32 | 33 | do { 34 | return try Session(fileUrl: url) 35 | } catch let error as Refactorer.InitializationError { 36 | importErrorMessage = error.description 37 | } catch let error { 38 | importErrorMessage = error.localizedDescription 39 | } 40 | 41 | return nil 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /COWRewriter/UI/BottomToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BottomToolbar.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | protocol BottomToolbarActions: AnyObject { 11 | 12 | func selectAll() 13 | 14 | func deselectAll() 15 | 16 | func copy() async 17 | 18 | func saveAs(url: URL) async throws 19 | 20 | } 21 | 22 | struct BottomToolbar: View { 23 | 24 | private struct Flags { 25 | 26 | var isSaving: Bool = false 27 | 28 | var isCopying: Bool = false 29 | 30 | } 31 | 32 | let actions: BottomToolbarActions 33 | 34 | @Binding 35 | var candidates: [RefactorCandidate] 36 | 37 | @Binding 38 | var refactorRequests: [RefactorRequest] 39 | 40 | @State 41 | private var flags: Flags = Flags() 42 | 43 | var body: some View { 44 | HStack { 45 | Button("Select All", action: selectAll) 46 | .disabled(hasSelectedAll || candidates.isEmpty) 47 | Button("Deselect All", action: deselectAll) 48 | .disabled(!hasSelectedAll || candidates.isEmpty) 49 | Spacer() 50 | Button("Copy ...", action: copy) 51 | .disabled(refactorRequests.isEmpty) 52 | Button("Save As ...", action: saveAs) 53 | .disabled(refactorRequests.isEmpty) 54 | } 55 | } 56 | 57 | private var hasSelectedAll: Bool { 58 | candidates.reduce(true, {$0 && $1.isSelected}) 59 | } 60 | 61 | private func selectAll() { 62 | actions.selectAll() 63 | } 64 | 65 | private func deselectAll() { 66 | actions.deselectAll() 67 | } 68 | 69 | private func copy() { 70 | guard !flags.isCopying else { 71 | return 72 | } 73 | flags.isCopying = true 74 | 75 | Task.detached { 76 | await actions.copy() 77 | flags.isCopying = false 78 | } 79 | } 80 | 81 | private func saveAs() { 82 | func getURL() -> URL? { 83 | let savePanel = NSSavePanel() 84 | savePanel.allowedContentTypes = [.swiftSource] 85 | savePanel.canCreateDirectories = true 86 | savePanel.isExtensionHidden = false 87 | savePanel.allowsOtherFileTypes = false 88 | let response = savePanel.runModal() 89 | return response == .OK ? savePanel.url : nil 90 | } 91 | 92 | guard let url = getURL() else { 93 | return 94 | } 95 | 96 | flags.isSaving = true 97 | Task.detached { 98 | try await actions.saveAs(url: url) 99 | flags.isSaving = false 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /COWRewriter/UI/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // COW Rewriter 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @EnvironmentObject 13 | var sessionManager: SessionManager 14 | 15 | var body: some View { 16 | refactorViewOrFileDropView 17 | .padding() 18 | .frame(maxWidth: .infinity, maxHeight: .infinity) 19 | } 20 | 21 | @ViewBuilder 22 | var refactorViewOrFileDropView: some View { 23 | if let session = sessionManager.currentSession { 24 | RefactorView(selectedFileURL: $sessionManager.selectedFileURL) 25 | .environmentObject(session) 26 | .transition(.opacity) 27 | } else { 28 | FileDropView(selectedFileURL: $sessionManager.selectedFileURL) 29 | .transition(.opacity) 30 | } 31 | } 32 | 33 | } 34 | 35 | struct ContentView_Previews: PreviewProvider { 36 | 37 | static var previews: some View { 38 | ContentView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /COWRewriter/UI/FileDropView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileDropView.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FileDropView: View { 11 | 12 | @Binding 13 | var selectedFileURL: URL? 14 | 15 | @State 16 | private var importErrorMessage: String? 17 | 18 | var body: some View { 19 | VStack(spacing: 8) { 20 | HStack { 21 | Button("Open", action: onTapOpen) 22 | .fixedSize() 23 | Text("or drop a Swift source file.") 24 | .lineLimit(1) 25 | .fixedSize(horizontal: true, vertical: true) 26 | } 27 | ImportErrorMessageView( 28 | importErrorMessage: $importErrorMessage, 29 | fadeEdge: .bottom 30 | ) 31 | } 32 | .frame( 33 | minWidth: 300, 34 | maxWidth: .infinity, 35 | minHeight: 150, 36 | maxHeight: .infinity 37 | ) 38 | .onDrop( 39 | of: [.fileURL], 40 | isTargeted: nil, 41 | perform: FilePicker.onDrop( 42 | url: animatedSelectedFileURL, 43 | errorMessage: animatedImportErrorMessage 44 | ) 45 | ) 46 | } 47 | 48 | private var animatedImportErrorMessage: Binding { 49 | $importErrorMessage.animation(.easeInOut) 50 | } 51 | 52 | private var animatedSelectedFileURL: Binding { 53 | $selectedFileURL.animation(.easeInOut) 54 | } 55 | 56 | private func onTapOpen() { 57 | guard let url = FilePicker.open() else { 58 | return 59 | } 60 | animatedSelectedFileURL.wrappedValue = url 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /COWRewriter/UI/FilePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilePicker.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import SwiftUI 9 | import AppKit 10 | import UniformTypeIdentifiers 11 | 12 | enum FileDropError: Error { 13 | 14 | case systemError(Error) 15 | 16 | case invalidDropData 17 | 18 | case notSwiftSource(url: URL) 19 | 20 | } 21 | 22 | struct FilePicker: View { 23 | 24 | @Binding 25 | var selectedFileURL: URL? 26 | 27 | @State 28 | private var isTargetedDrop: Bool = true 29 | 30 | @State 31 | private var importErrorMessage: String? = nil 32 | 33 | var body: some View { 34 | VStack { 35 | HStack(spacing: 16) { 36 | HStack { 37 | if let path = selectedFileURL?.path { 38 | Text(path) 39 | } else { 40 | Text("No file open") 41 | .foregroundColor(.gray) 42 | } 43 | Spacer() 44 | } 45 | Button( 46 | selectedFileURL == nil ? "Open" : "Open Another", 47 | action: onTapSelectFile 48 | ) 49 | } 50 | ImportErrorMessageView( 51 | importErrorMessage: animatedImportErrorMessage, 52 | fadeEdge: .top 53 | ) 54 | } 55 | .onDrop( 56 | of: [.fileURL], 57 | isTargeted: nil, 58 | perform: FilePicker.onDrop( 59 | url: animatedSelectedFileURL, 60 | errorMessage: animatedImportErrorMessage 61 | ) 62 | ) 63 | } 64 | 65 | private func onTapSelectFile() { 66 | if let url = Self.open() { 67 | self.selectedFileURL = url 68 | } 69 | } 70 | 71 | static func open() -> URL? { 72 | let panel = NSOpenPanel() 73 | panel.allowedContentTypes = [.swiftSource] 74 | panel.allowsMultipleSelection = false 75 | panel.canChooseDirectories = false 76 | guard panel.runModal() == .OK else { 77 | return nil 78 | } 79 | return panel.url 80 | } 81 | 82 | private var animatedImportErrorMessage: Binding { 83 | $importErrorMessage.animation(.easeInOut) 84 | } 85 | 86 | private var animatedSelectedFileURL: Binding { 87 | $selectedFileURL.animation(.easeInOut) 88 | } 89 | 90 | // MARK: Drag & Drop Support 91 | 92 | // TODO: Reuse data in memory when dropping the same url? 93 | static func onDrop( 94 | url: Binding, 95 | errorMessage: Binding 96 | ) -> ([NSItemProvider]) -> Bool { 97 | 98 | @Sendable 99 | @MainActor 100 | func updateUrl(_ droppedUrl: URL) { 101 | url.wrappedValue = droppedUrl 102 | } 103 | 104 | @Sendable 105 | @MainActor 106 | func updateErrorMessage(_ string: String) { 107 | errorMessage.wrappedValue = string 108 | } 109 | 110 | return { (providers: [NSItemProvider]) -> Bool in 111 | Task { 112 | do { 113 | let loadedItem = try await providers.first?.loadItem( 114 | forTypeIdentifier: UTType.fileURL.identifier 115 | ) 116 | 117 | guard let data = loadedItem as? Data, 118 | let path = String(data: data, encoding: .utf8), 119 | let droppedUrl = URL(string: path) else { 120 | await updateErrorMessage("Invalid dropped contents.") 121 | return 122 | } 123 | 124 | let urlType = try? droppedUrl 125 | .resourceValues(forKeys: [.typeIdentifierKey]) 126 | .typeIdentifier 127 | 128 | guard urlType == UTType.swiftSource.identifier else { 129 | await updateErrorMessage("\(droppedUrl.path) is not a Swift source file.") 130 | return 131 | } 132 | 133 | await updateUrl(droppedUrl) 134 | } catch let error { 135 | await updateErrorMessage(error.localizedDescription) 136 | } 137 | } 138 | 139 | return true 140 | } 141 | } 142 | 143 | } 144 | 145 | 146 | struct FilePicker_Previews: PreviewProvider { 147 | 148 | static var previews: some View { 149 | FilePicker(selectedFileURL: .constant(nil)) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /COWRewriter/UI/ImportErrorMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImportErrorMessageView.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ImportErrorMessageView: View { 11 | 12 | @Binding 13 | var importErrorMessage: String? 14 | 15 | let fadeEdge: Edge 16 | 17 | var body: some View { 18 | if let importErrorMessage = importErrorMessage { 19 | HStack { 20 | Image(systemName: "xmark.octagon.fill") 21 | .foregroundColor(.yellow) 22 | Text(importErrorMessage) 23 | Button(action: onTapClearImportErrorMessage) { 24 | Image(systemName: "xmark.circle") 25 | }.buttonStyle(.borderless) 26 | } 27 | .transition(.opacity.combined(with: .move(edge: fadeEdge))) 28 | } 29 | } 30 | 31 | private func onTapClearImportErrorMessage() { 32 | animatedImportErrorMessage.wrappedValue = nil 33 | } 34 | 35 | private var animatedImportErrorMessage: Binding { 36 | $importErrorMessage.animation(.easeInOut) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /COWRewriter/UI/RefactorRequestsConfigView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefactorRequestsConfigView.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RefactorRequestsConfigView: View { 11 | 12 | @Binding 13 | var candidates: [RefactorCandidate] 14 | 15 | @Binding 16 | var selectedCandidates: Set 17 | 18 | @Binding 19 | var refactorRequestConfigs: [RefactorRequestConfig] 20 | 21 | @Binding 22 | var printerConfigs: PrinterConfigs 23 | 24 | @Binding 25 | var contentsPreview: String 26 | 27 | @State 28 | private var highlightedDeclID: UUID? = nil 29 | 30 | @State 31 | private var showPreviewOptions: Bool = false 32 | 33 | @State 34 | private var showUnresolvedSemanticsList: Bool = false 35 | 36 | var body: some View { 37 | HSplitView { 38 | refactorableTypeList 39 | .minSizeBoundedSplitContent(at: .first) 40 | if showUnresolvedSemanticsList { 41 | unresolvedSemanticsList 42 | .minSizeBoundedSplitContent(at: .intermediate) 43 | } 44 | refactorPreviewView 45 | .minSizeBoundedSplitContent(at: .last) 46 | } 47 | .onChange(of: highlightedDeclID, perform: onHighlightedDeclIDChange) 48 | .onChange(of: selectedCandidates) { _ in 49 | updateSplitContents() 50 | } 51 | .onAppear { 52 | updateSplitContents() 53 | } 54 | } 55 | 56 | @ViewBuilder 57 | private var refactorableTypeList: some View { 58 | VStack { 59 | ColumnLabel { 60 | Text("Refactorable Types") 61 | .font(.system(.callout)) 62 | } 63 | .padding(8) 64 | Table($candidates, selection: $highlightedDeclID) { 65 | TableColumn("Refactor") { $decl in 66 | TableColumnContentToggle( 67 | isOn: $decl.isSelected, 68 | isAvailable: highlightedDeclID == $decl.wrappedValue.id 69 | ) 70 | } 71 | .width(50) 72 | TableColumn("Type Name", value: \.wrappedValue.content.identifier) 73 | } 74 | .border(Color.secondary) 75 | } 76 | } 77 | 78 | @ViewBuilder 79 | private var unresolvedSemanticsList: some View { 80 | VStack { 81 | ColumnLabel { 82 | Text("Unresolved Semantics") 83 | .font(.system(.callout)) 84 | } 85 | .padding(8) 86 | List { 87 | ForEach($refactorRequestConfigs.filter(isOfSelectedCandidate)) { $group in 88 | Section("struct \(group.declName)") { 89 | ForEach($group.unresolvedSemanticsItems) { $item in 90 | UnresolvedSemanticsItemView($item) 91 | } 92 | } 93 | .transition(.opacity.combined(with: .move(edge: .top))) 94 | } 95 | } 96 | .border(Color.secondary) 97 | } 98 | } 99 | 100 | @ViewBuilder 101 | private var refactorPreviewView: some View { 102 | VStack { 103 | HStack { 104 | ColumnLabel { 105 | Text("Preview") 106 | .font(.system(.callout)) 107 | } 108 | Spacer() 109 | Toggle("Options", isOn: $showPreviewOptions.animation()) 110 | .toggleStyle(.button) 111 | } 112 | .padding(8) 113 | VStack { 114 | if showPreviewOptions { 115 | previewOptionsView 116 | .transition(.opacity.combined(with: .move(edge: .top))) 117 | } 118 | TextEditor(text: .constant(contentsPreview)) 119 | .frame(maxWidth: .infinity) 120 | .font(Font.system(.body).monospaced()) 121 | .lineLimit(nil) 122 | .border(Color.secondary) 123 | }.clipped() 124 | } 125 | } 126 | 127 | @ViewBuilder 128 | private var previewOptionsView: some View { 129 | VStack(alignment: .leading) { 130 | Picker("Prefer Indent Using: ", selection: $printerConfigs.indentationMode) { 131 | ForEach(PrinterConfigs.IndentationMode.allCases, id: \.self) { eachCase in 132 | Text(eachCase.displayName) 133 | } 134 | } 135 | HStack { 136 | Text("Tab Width: ") 137 | Stepper(value: $printerConfigs.tabWidth, in: 1...(.max)) { 138 | TextField( 139 | "", 140 | text: Binding { 141 | String(printerConfigs.tabWidth) 142 | } set: { newValue in 143 | guard let newIntValue = Int(newValue) else { 144 | return 145 | } 146 | printerConfigs.tabWidth = newIntValue 147 | } 148 | ).frame(maxWidth: 20) 149 | } 150 | Text("spaces") 151 | } 152 | HStack { 153 | Text("Indent Width: ") 154 | Stepper(value: $printerConfigs.indentWidth, in: 1...(.max)) { 155 | TextField( 156 | "", 157 | text: Binding { 158 | String(printerConfigs.indentWidth) 159 | } set: { newValue in 160 | guard let newIntValue = Int(newValue) else { 161 | return 162 | } 163 | printerConfigs.indentWidth = newIntValue 164 | } 165 | ).frame(maxWidth: 20) 166 | } 167 | Text("spaces") 168 | } 169 | } 170 | } 171 | 172 | private func onHighlightedDeclIDChange(_ id: UUID?) { 173 | guard let id = id else { 174 | return 175 | } 176 | updateHighlightId(id) 177 | } 178 | 179 | private func isOfSelectedCandidate(_ config: RefactorRequestConfig) -> Bool { 180 | selectedCandidates.contains(config.id) 181 | } 182 | 183 | private func isOfSelectedCandidate(_ config: Binding) -> Bool { 184 | selectedCandidates.contains(config.id) 185 | } 186 | 187 | private func updateSplitContents() { 188 | showUnresolvedSemanticsList = refactorRequestConfigs 189 | .filter(isOfSelectedCandidate) 190 | .reduce(false) { partial, config in 191 | partial || !config.unresolvedSemanticsItems.isEmpty 192 | } 193 | } 194 | 195 | private func updateHighlightId(_ id: UUID) { 196 | 197 | } 198 | 199 | } 200 | 201 | private enum SplitContentPosition { 202 | case first 203 | case intermediate 204 | case last 205 | 206 | var edges: Edge.Set { 207 | switch self { 208 | case .first: return .trailing 209 | case .intermediate: return [.leading, .trailing] 210 | case .last: return .leading 211 | } 212 | } 213 | 214 | } 215 | 216 | extension View { 217 | 218 | fileprivate func minSizeBoundedSplitContent( 219 | at position: SplitContentPosition 220 | ) -> some View { 221 | frame(minWidth: 300, minHeight: 500) 222 | .padding(position.edges, 8) 223 | } 224 | 225 | } 226 | 227 | private struct ColumnLabel: View { 228 | 229 | let label: Label 230 | 231 | init(@ViewBuilder label: () -> Label) { 232 | self.label = label() 233 | } 234 | 235 | var body: some View { 236 | HStack { 237 | label 238 | .font(.system(.callout)) 239 | Spacer() 240 | } 241 | } 242 | 243 | } 244 | 245 | private struct TableColumnContentToggle: View { 246 | 247 | @Binding 248 | var isOn: Bool 249 | 250 | let isAvailable: Bool 251 | 252 | var body: some View { 253 | HStack { 254 | Spacer() 255 | Toggle(isOn: $isOn, label: { }) 256 | .keyboardShortcut(.space, modifiers: [], when: isAvailable) 257 | .labelsHidden() 258 | Spacer() 259 | } 260 | } 261 | 262 | } 263 | 264 | private enum UnresolvedSemanticsItemView: View { 265 | 266 | typealias Item = RefactorRequestConfig.UnresolvedSemanticsItem 267 | 268 | case name(NamingIssueView) 269 | 270 | case typeAnnotation(TypeAnnotationIssueView) 271 | 272 | var body: some View { 273 | switch self { 274 | case let .name(view): 275 | view 276 | case let .typeAnnotation(view): 277 | view 278 | } 279 | } 280 | 281 | init(_ item: Binding) { 282 | switch item.wrappedValue { 283 | case let .name(issue): 284 | self = .name( 285 | NamingIssueView( 286 | issue: Binding( 287 | get: { issue }, 288 | set: { newValue, transaction in 289 | item.wrappedValue = .name(newValue) 290 | } 291 | ) 292 | ) 293 | ) 294 | case let .typeAnnotation(issue): 295 | self = .typeAnnotation( 296 | TypeAnnotationIssueView( 297 | issue: Binding( 298 | get: { issue }, 299 | set: { newValue, transaction in 300 | item.wrappedValue = .typeAnnotation(newValue) 301 | } 302 | ) 303 | ) 304 | ) 305 | } 306 | } 307 | 308 | struct NamingIssueView: View { 309 | 310 | typealias Issue = Item.NamingIssue 311 | 312 | @Binding 313 | var issue: Issue 314 | 315 | var body: some View { 316 | VStack(alignment: .leading) { 317 | Text(issue.key.rawValue) 318 | TextField( 319 | issue.suggestedName ?? "", 320 | text: $issue.userSpecifiedName 321 | ) 322 | } 323 | } 324 | 325 | } 326 | 327 | struct TypeAnnotationIssueView: View { 328 | 329 | typealias Issue = Item.TypeAnnotationIssue 330 | 331 | @Binding 332 | var issue: Issue 333 | 334 | var body: some View { 335 | VStack(alignment: .leading) { 336 | Text("\(issue.letOrVar) \(issue.name): ") 337 | TextField( 338 | issue.suggestedType?.description ?? "Missing type", 339 | text: $issue.userType 340 | ) 341 | } 342 | } 343 | 344 | } 345 | 346 | } 347 | -------------------------------------------------------------------------------- /COWRewriter/UI/RefactorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefactorView.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RefactorView: View { 11 | 12 | @Binding 13 | var selectedFileURL: URL? 14 | 15 | @EnvironmentObject 16 | var session: Session 17 | 18 | var body: some View { 19 | FilePicker(selectedFileURL: $selectedFileURL) 20 | RefactorRequestsConfigView( 21 | candidates: $session.candidates, 22 | selectedCandidates: $session.selectedCandidates, 23 | refactorRequestConfigs: $session.refactorRequestConfigs, 24 | printerConfigs: $session.printerConfigs, 25 | contentsPreview: $session.contentsPreview 26 | ) 27 | BottomToolbar( 28 | actions: session, 29 | candidates: $session.candidates, 30 | refactorRequests: $session.refactorRequests 31 | ) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /COWRewriter/UI/TheApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TheApp.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TheApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | .environmentObject(SessionManager()) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /COWRewriter/Utilities/Binding+COWRewriterAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Binding+COWRewriterAdditions.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/9/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Binding { 11 | 12 | func animated(_ animated: Bool) -> Binding { 13 | if animated { 14 | return animation() 15 | } 16 | return self 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /COWRewriter/Utilities/ConditionalKeyboardShorcut.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionalKeyboardShorcut.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/5/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | 11 | extension View { 12 | 13 | func keyboardShortcut( 14 | _ key: KeyEquivalent, 15 | modifiers: EventModifiers = .command, 16 | when isAvailable: Bool 17 | ) -> some View { 18 | modifier( 19 | ConditionalKeyboardShorcut( 20 | isAvailable: isAvailable, 21 | keyboardShortcut: KeyboardShortcut(key, modifiers: modifiers) 22 | ) 23 | ) 24 | } 25 | 26 | } 27 | 28 | 29 | struct ConditionalKeyboardShorcut: ViewModifier { 30 | 31 | let isAvailable: Bool 32 | 33 | let keyboardShortcut: KeyboardShortcut 34 | 35 | func body(content: Content) -> some View { 36 | if isAvailable { 37 | content 38 | .keyboardShortcut( 39 | keyboardShortcut.key, 40 | modifiers: keyboardShortcut.modifiers, 41 | localization: keyboardShortcut.localization 42 | ) 43 | } else { 44 | content 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /COWRewriter/Utilities/RuntimeErrorHandling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuntimeErrorHandling.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/1/22. 6 | // 7 | 8 | import OSLog 9 | 10 | func notImplemented() -> Never { 11 | fatalError() 12 | } 13 | 14 | func notImplemented() -> T { 15 | fatalError() 16 | } 17 | 18 | func abstract() -> Never { 19 | fatalError() 20 | } 21 | 22 | func unreachable() -> Never { 23 | fatalError() 24 | } 25 | -------------------------------------------------------------------------------- /COWRewriter/Utilities/Stdlib+COWRewriterAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stdlib+COWRewriterAdditions.swift 3 | // COWRewriter 4 | // 5 | // Created by WeZZard on 6/4/22. 6 | // 7 | 8 | extension Sequence { 9 | 10 | @inlinable 11 | func sorted(by keyPath: KeyPath) -> [Element] { 12 | return sorted { a, b in 13 | return a[keyPath: keyPath] < b[keyPath: keyPath] 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /COWRewriterTests/Reafactorer/COWRewriterTestsBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // COWRewriterTestsBase.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/22/22. 6 | // 7 | 8 | import XCTest 9 | import SwiftSyntax 10 | import SwiftSyntaxParser 11 | 12 | @testable 13 | import COWRewriter 14 | 15 | class COWRewriterTestsBase: XCTestCase { 16 | 17 | internal func evaluate(source: String, expected: String, file: StaticString = #file, line: UInt = #line) async { 18 | 19 | class Context: COWRewriterInputContext, SemaInputting, SemaOutputting { 20 | 21 | let file: String? 22 | 23 | var treeID: UInt 24 | 25 | var tree: SourceFileSyntax 26 | 27 | let slc: SourceLocationConverter 28 | 29 | var refactorableDecls: [RefactorableDecl] 30 | 31 | @inline(__always) 32 | init(tree: SourceFileSyntax) { 33 | self.file = nil 34 | self.treeID = UInt.random(in: .min...(.max)) 35 | self.tree = tree 36 | self.slc = SourceLocationConverter(file: "IN_MEMORY_SOURCE", tree: tree) 37 | self.refactorableDecls = [] 38 | } 39 | 40 | } 41 | 42 | do { 43 | let tree = try SyntaxParser.parse(source: source) 44 | 45 | let context = Context(tree: tree) 46 | let sema = Sema(target: .host, input: context, output: context) 47 | let rewriter = COWRewriter(input: context) 48 | 49 | 50 | sema.performIfNeeded() 51 | 52 | let requests = context.refactorableDecls.map { 53 | RefactorRequest( 54 | decl: $0, 55 | storageClassName: $0.namingSuggestions[.storageClassName] ?? "Storage", 56 | storageVariableName: $0.namingSuggestions[.storageVariableName] ?? "storage", 57 | storageUniquificationFunctionName: $0.namingSuggestions[.storageUniquificationFunctionName] ?? "makeUniqueStorageIfNeeded", 58 | typedefs: [:] 59 | ) 60 | } 61 | let output = rewriter.execute(requests: requests) 62 | 63 | let printer = Printer() 64 | printer.configs.indentationMode = .space 65 | printer.configs.indentWidth = 2 66 | 67 | let actual = printer.print(syntax: output, url: URL(fileURLWithPath: "")) 68 | 69 | XCTAssertStringsEqualWithDiff(actual, expected, file: file, line: line) 70 | } catch let error { 71 | XCTFail(error.localizedDescription, file: file, line: line) 72 | } 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /COWRewriterTests/Reafactorer/COWRewriterWontRewriteTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // COWRewriterWontRewriteTests.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/22/22. 6 | // 7 | 8 | import XCTest 9 | 10 | class COWRewriterWontRewriteTests: COWRewriterTestsBase { 11 | 12 | func testWontRewriteEmptyStruct() async { 13 | let source = """ 14 | struct Foo { 15 | } 16 | """ 17 | 18 | let expected = """ 19 | struct Foo { 20 | } 21 | 22 | """ 23 | 24 | await evaluate(source: source, expected: expected) 25 | } 26 | 27 | func testWontRewriteStructWithUniqueComputedProperty() async { 28 | let source = """ 29 | struct Foo { 30 | 31 | var value: Int { 0 } 32 | 33 | } 34 | """ 35 | 36 | let expected = """ 37 | struct Foo { 38 | 39 | var value: Int { 0 } 40 | 41 | } 42 | 43 | """ 44 | 45 | await evaluate(source: source, expected: expected) 46 | } 47 | 48 | func testWontRewriteStructWithDedicatedMultipleComputedProperty() async { 49 | let source = """ 50 | struct Foo { 51 | 52 | var fee: Int { 0 } 53 | 54 | var foe: Int { 0 } 55 | 56 | var fum: Int { 0 } 57 | 58 | } 59 | """ 60 | 61 | let expected = """ 62 | struct Foo { 63 | 64 | var fee: Int { 0 } 65 | 66 | var foe: Int { 0 } 67 | 68 | var fum: Int { 0 } 69 | 70 | } 71 | 72 | """ 73 | 74 | await evaluate(source: source, expected: expected) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /COWRewriterTests/Reafactorer/COWRewritertRewriteTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // COWRewriterRewriteTests.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/22/22. 6 | // 7 | 8 | import XCTest 9 | 10 | class COWRewriterRewriteTests: COWRewriterTestsBase { 11 | 12 | func testRewritesStructWithSingleStoredProperty() async { 13 | let source = """ 14 | struct Foo { 15 | var foo: Int 16 | 17 | init(foo: Int) { 18 | self.foo = foo 19 | } 20 | 21 | } 22 | """ 23 | 24 | let expected = """ 25 | struct Foo { 26 | private class Storage { 27 | var foo: Int 28 | 29 | init(foo: Int) { 30 | self.foo = foo 31 | } 32 | 33 | init(_ storage: Storage) { 34 | self.foo = storage.foo 35 | } 36 | } 37 | 38 | private var storage: Storage 39 | 40 | private mutating func makeUniqueStorageIfNeeded() { 41 | guard !isKnownUniquelyReferenced(&storage) else { return } 42 | self.storage = Storage(storage) 43 | } 44 | 45 | var foo: Int { 46 | _read { yield self.storage.foo } 47 | _modify { 48 | self.makeUniqueStorageIfNeeded() 49 | yield &self.storage.foo 50 | } 51 | } 52 | 53 | init(foo: Int) { 54 | self.storage = Storage(foo: foo) 55 | } 56 | } 57 | 58 | """ 59 | 60 | await evaluate(source: source, expected: expected) 61 | } 62 | 63 | func testRewriteContextRewritesStructWithAllStoredProperties() async { 64 | let source = """ 65 | struct Foo { 66 | var fee: Int 67 | 68 | var foe: Int 69 | 70 | var fum: Int 71 | 72 | init(fee: Int, foe: Int, fum: Int) { 73 | self.fee = fee 74 | self.foe = foe 75 | self.fum = fum 76 | } 77 | } 78 | """ 79 | 80 | let expected = """ 81 | struct Foo { 82 | private class Storage { 83 | var fee: Int 84 | 85 | var foe: Int 86 | 87 | var fum: Int 88 | 89 | init(fee: Int, foe: Int, fum: Int) { 90 | self.fee = fee 91 | self.foe = foe 92 | self.fum = fum 93 | } 94 | 95 | init(_ storage: Storage) { 96 | self.fee = storage.fee 97 | self.foe = storage.foe 98 | self.fum = storage.fum 99 | } 100 | } 101 | 102 | private var storage: Storage 103 | 104 | private mutating func makeUniqueStorageIfNeeded() { 105 | guard !isKnownUniquelyReferenced(&storage) else { return } 106 | self.storage = Storage(storage) 107 | } 108 | 109 | var fee: Int { 110 | _read { yield self.storage.fee } 111 | _modify { 112 | self.makeUniqueStorageIfNeeded() 113 | yield &self.storage.fee 114 | } 115 | } 116 | 117 | var foe: Int { 118 | _read { yield self.storage.foe } 119 | _modify { 120 | self.makeUniqueStorageIfNeeded() 121 | yield &self.storage.foe 122 | } 123 | } 124 | 125 | var fum: Int { 126 | _read { yield self.storage.fum } 127 | _modify { 128 | self.makeUniqueStorageIfNeeded() 129 | yield &self.storage.fum 130 | } 131 | } 132 | 133 | init(fee: Int, foe: Int, fum: Int) { 134 | self.storage = Storage(fee: fee, foe: foe, fum: fum) 135 | } 136 | } 137 | 138 | """ 139 | 140 | await evaluate(source: source, expected: expected) 141 | } 142 | 143 | func testRewritesStructWithStoredPropertiesAndLeftComputedPropertiesAlone() async { 144 | let source = """ 145 | struct Foo { 146 | var fee: Int 147 | 148 | var foe: Int 149 | 150 | var fum: Int 151 | 152 | var bar: Int { 0 } 153 | 154 | init(fee: Int, foe: Int, fum: Int) { 155 | self.fee = fee 156 | self.foe = foe 157 | self.fum = fum 158 | } 159 | } 160 | """ 161 | 162 | let expected = """ 163 | struct Foo { 164 | private class Storage { 165 | var fee: Int 166 | 167 | var foe: Int 168 | 169 | var fum: Int 170 | 171 | init(fee: Int, foe: Int, fum: Int) { 172 | self.fee = fee 173 | self.foe = foe 174 | self.fum = fum 175 | } 176 | 177 | init(_ storage: Storage) { 178 | self.fee = storage.fee 179 | self.foe = storage.foe 180 | self.fum = storage.fum 181 | } 182 | } 183 | 184 | private var storage: Storage 185 | 186 | private mutating func makeUniqueStorageIfNeeded() { 187 | guard !isKnownUniquelyReferenced(&storage) else { return } 188 | self.storage = Storage(storage) 189 | } 190 | 191 | var fee: Int { 192 | _read { yield self.storage.fee } 193 | _modify { 194 | self.makeUniqueStorageIfNeeded() 195 | yield &self.storage.fee 196 | } 197 | } 198 | 199 | var foe: Int { 200 | _read { yield self.storage.foe } 201 | _modify { 202 | self.makeUniqueStorageIfNeeded() 203 | yield &self.storage.foe 204 | } 205 | } 206 | 207 | var fum: Int { 208 | _read { yield self.storage.fum } 209 | _modify { 210 | self.makeUniqueStorageIfNeeded() 211 | yield &self.storage.fum 212 | } 213 | } 214 | 215 | init(fee: Int, foe: Int, fum: Int) { 216 | self.storage = Storage(fee: fee, foe: foe, fum: fum) 217 | } 218 | 219 | var bar: Int { 0 } 220 | } 221 | 222 | """ 223 | 224 | await evaluate(source: source, expected: expected) 225 | } 226 | 227 | func testRewritesStructWithUniqueStoredPropertyAndExsitedNestedStorageClass() async { 228 | let source = """ 229 | struct Foo { 230 | var fee: Int 231 | 232 | var foe: Int 233 | 234 | var fum: Int 235 | 236 | class Storage { 237 | } 238 | 239 | init(fee: Int, foe: Int, fum: Int) { 240 | self.fee = fee 241 | self.foe = foe 242 | self.fum = fum 243 | } 244 | } 245 | """ 246 | 247 | let expected = """ 248 | struct Foo { 249 | private class Storage2 { 250 | var fee: Int 251 | 252 | var foe: Int 253 | 254 | var fum: Int 255 | 256 | init(fee: Int, foe: Int, fum: Int) { 257 | self.fee = fee 258 | self.foe = foe 259 | self.fum = fum 260 | } 261 | 262 | init(_ storage: Storage2) { 263 | self.fee = storage.fee 264 | self.foe = storage.foe 265 | self.fum = storage.fum 266 | } 267 | } 268 | 269 | private var storage: Storage2 270 | 271 | private mutating func makeUniqueStorageIfNeeded() { 272 | guard !isKnownUniquelyReferenced(&storage) else { return } 273 | self.storage = Storage2(storage) 274 | } 275 | 276 | var fee: Int { 277 | _read { yield self.storage.fee } 278 | _modify { 279 | self.makeUniqueStorageIfNeeded() 280 | yield &self.storage.fee 281 | } 282 | } 283 | 284 | var foe: Int { 285 | _read { yield self.storage.foe } 286 | _modify { 287 | self.makeUniqueStorageIfNeeded() 288 | yield &self.storage.foe 289 | } 290 | } 291 | 292 | var fum: Int { 293 | _read { yield self.storage.fum } 294 | _modify { 295 | self.makeUniqueStorageIfNeeded() 296 | yield &self.storage.fum 297 | } 298 | } 299 | 300 | init(fee: Int, foe: Int, fum: Int) { 301 | self.storage = Storage2(fee: fee, foe: foe, fum: fum) 302 | } 303 | 304 | class Storage { 305 | } 306 | } 307 | 308 | """ 309 | 310 | await evaluate(source: source, expected: expected) 311 | } 312 | 313 | } 314 | -------------------------------------------------------------------------------- /COWRewriterTests/Sema/SemaRefactorableDeclRecognitionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemaRefactorableDeclRecognitionTests.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/26/22. 6 | // 7 | 8 | import XCTest 9 | import SwiftSyntax 10 | import SwiftSyntaxParser 11 | import SwiftSyntaxBuilder 12 | 13 | @testable 14 | import COWRewriter 15 | 16 | class SemaRefactorableDeclRecognitionTests: SemaTestsBase { 17 | 18 | // MARK: Won't Recognize 19 | 20 | func testWontRecognizeEmptyStruct() { 21 | let source = """ 22 | struct Foo { 23 | 24 | } 25 | """ 26 | withSource(source) 27 | .evaluate(.refactorableDecls) 28 | } 29 | 30 | func testWontRecognizeStructWithoutStoredProperties() { 31 | let source = """ 32 | struct Foo { 33 | 34 | var bar: Bool { return false } 35 | 36 | } 37 | """ 38 | withSource(source) 39 | .evaluate(.refactorableDecls) 40 | } 41 | 42 | // MARK: Recognizes 43 | 44 | func testRecognizesStructWithStoredProperties() { 45 | let source = """ 46 | struct Foo { 47 | 48 | var value: Int = 0 49 | 50 | } 51 | """ 52 | withSource(source) 53 | .expectRefactorableDecl("Foo") 54 | .evaluate(.refactorableDecls) 55 | } 56 | 57 | func testRecognizesNestedStructWithStoredProperties() { 58 | let source = """ 59 | struct Foo { 60 | 61 | var value: Int = 0 62 | 63 | struct Bar { 64 | 65 | var value: Int = 0 66 | 67 | } 68 | } 69 | """ 70 | withSource(source) 71 | .expectRefactorableDecl("Foo") 72 | .expectRefactorableDecl("Bar") 73 | .evaluate(.refactorableDecls) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /COWRewriterTests/Sema/SemaTestsBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemaTestsBase.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/26/22. 6 | // 7 | 8 | import XCTest 9 | import SwiftSyntax 10 | import SwiftSyntaxParser 11 | import SwiftSyntaxBuilder 12 | 13 | @testable 14 | import COWRewriter 15 | 16 | class SemaTestsBase: XCTestCase { 17 | 18 | class EvaluationRequestBuilder { 19 | 20 | private struct TypeCheckResult { 21 | let type: String? 22 | let file: StaticString 23 | let line: UInt 24 | } 25 | 26 | private struct RefactorableDeclDetectionResult { 27 | let identifier: String 28 | let file: StaticString 29 | let line: UInt 30 | } 31 | 32 | let source: String 33 | 34 | private var expectedTypeCheckResults: [GlobalIdentifier : TypeCheckResult] 35 | 36 | private var ignoredIdentifiers: Set 37 | 38 | private var expectRefactorableDecls: [RefactorableDeclDetectionResult] 39 | 40 | init(source: String) { 41 | self.source = source 42 | self.expectedTypeCheckResults = [:] 43 | self.ignoredIdentifiers = [] 44 | self.expectRefactorableDecls = [] 45 | } 46 | 47 | func expectTypeChecking( 48 | _ identifier: GlobalIdentifier, 49 | with type: String?, 50 | file: StaticString = #file, 51 | line: UInt = #line 52 | ) -> EvaluationRequestBuilder { 53 | expectedTypeCheckResults[identifier] = TypeCheckResult( 54 | type: type, 55 | file: file, 56 | line: line 57 | ) 58 | return self 59 | } 60 | 61 | func ignoreTypeChecking( 62 | _ identifier: GlobalIdentifier 63 | ) -> EvaluationRequestBuilder { 64 | ignoredIdentifiers.insert(identifier) 65 | return self 66 | } 67 | 68 | func expectRefactorableDecl( 69 | _ identifier: String, 70 | file: StaticString = #file, 71 | line: UInt = #line 72 | ) -> EvaluationRequestBuilder { 73 | expectRefactorableDecls.append( 74 | RefactorableDeclDetectionResult( 75 | identifier: identifier, 76 | file: file, 77 | line: line 78 | ) 79 | ) 80 | return self 81 | } 82 | 83 | struct EvaluationOptions: OptionSet { 84 | 85 | typealias RawValue = UInt8 86 | 87 | var rawValue: RawValue 88 | 89 | init(rawValue: RawValue) { 90 | self.rawValue = rawValue 91 | } 92 | 93 | static let typeCheck = EvaluationOptions(rawValue: 0x1 << 0) 94 | static let refactorableDecls = EvaluationOptions(rawValue: 0x1 << 1) 95 | static let all: EvaluationOptions = [typeCheck, refactorableDecls] 96 | 97 | } 98 | 99 | func evaluate( 100 | _ options: EvaluationOptions = .all, 101 | file: StaticString = #file, 102 | line: UInt = #line 103 | ) { 104 | guard !options.isEmpty else { 105 | return 106 | } 107 | 108 | do { 109 | 110 | let context = try Context(source: source) 111 | let sema = Sema(target: .of64Bit, input: context, output: context) 112 | sema.performIfNeeded() 113 | 114 | if options.contains(.typeCheck) { 115 | evaluateTypeCheck(tree: sema.output.tree, file: file, line: line) 116 | } 117 | if options.contains(.refactorableDecls) { 118 | evaluateRefactorableDeclsDetection(actual: sema.output.refactorableDecls, file: file, line: line) 119 | } 120 | 121 | } catch let error { 122 | XCTFail(error.localizedDescription, file: file, line: line) 123 | } 124 | } 125 | 126 | private func evaluateTypeCheck(tree: SourceFileSyntax, file: StaticString, line: UInt) { 127 | let reader = BindingsReader(tree: tree) 128 | reader.readIfNeeded() 129 | for (identifier, eachExpectation) in expectedTypeCheckResults { 130 | if let readType = reader.bindings[identifier] { 131 | switch (readType, eachExpectation.type) { 132 | case let (.some(readType), .some(builtType)): 133 | XCTAssertEqual( 134 | readType, 135 | builtType, 136 | "Type bound to \"\(identifier) : \(readType)\" is different from the expectation: \"\(builtType)\".", 137 | file: eachExpectation.file, 138 | line: eachExpectation.line 139 | ) 140 | case let (.some(readType), .none): 141 | XCTFail( 142 | "Type bound to \"\(identifier) : \(readType)\" is different from the expectation: nil.", 143 | file: eachExpectation.file, 144 | line: eachExpectation.line 145 | ) 146 | case let (.none, .some(builtType)): 147 | XCTFail( 148 | "No type bound to \"\(identifier)\". Expecting \"\(builtType)\".", 149 | file: eachExpectation.file, 150 | line: eachExpectation.line 151 | ) 152 | case (.none, .none): 153 | break 154 | } 155 | } else { 156 | XCTFail( 157 | "\"\(identifier)\" has not read. There may be some issues in \(_typeName(BindingsReader.self)) or you need to check your expectations.", 158 | file: eachExpectation.file, 159 | line: eachExpectation.line 160 | ) 161 | } 162 | } 163 | for (identifier, readType) in reader.bindings { 164 | if !expectedTypeCheckResults.keys.contains(identifier) && !ignoredIdentifiers.contains(identifier) { 165 | if let readType = readType { 166 | XCTFail( 167 | "\"\(identifier) : \(readType)\" has read but not found in expectations. There may be some issues in \(_typeName(BindingsReader.self)) or you need to check your expectations.", 168 | file: file, 169 | line: line 170 | ) 171 | } else { 172 | XCTFail( 173 | "\"\(identifier)\" has read but not found in expectations. There may be some issues in \(_typeName(BindingsReader.self)) or you need to check your expectations.", 174 | file: file, 175 | line: line 176 | ) 177 | } 178 | } 179 | } 180 | } 181 | 182 | private func evaluateRefactorableDeclsDetection(actual: [RefactorableDecl], file: StaticString, line: UInt) { 183 | for eachExpected in expectRefactorableDecls { 184 | if !actual.contains(where: {$0.identifier == eachExpected.identifier}) { 185 | XCTFail( 186 | "Expected refactorable decl of identifier \"\(eachExpected.identifier)\" is not detected.", 187 | file: eachExpected.file, 188 | line: eachExpected.line 189 | ) 190 | } 191 | } 192 | for eachActual in actual { 193 | if !expectRefactorableDecls.contains(where: {$0.identifier == eachActual.identifier}) { 194 | XCTFail( 195 | "Unexpected refactorable decl of identifier \"\(eachActual.identifier)\" detected.", 196 | file: file, 197 | line: line 198 | ) 199 | } 200 | } 201 | } 202 | 203 | } 204 | 205 | internal func withSource(_ source: String) -> EvaluationRequestBuilder { 206 | EvaluationRequestBuilder(source: source) 207 | } 208 | 209 | internal typealias Bindings = [GlobalIdentifier : String?] 210 | 211 | @resultBuilder 212 | internal struct BindingsBuilder { 213 | 214 | static func buildBlock(_ components: (String, String?)...) -> Bindings { 215 | Bindings(uniqueKeysWithValues: components) 216 | } 217 | 218 | } 219 | 220 | /// A simple, global unique and human readable string for an identifier. 221 | typealias GlobalIdentifier = String 222 | 223 | private class Context: SemaInputting, SemaOutputting { 224 | 225 | var tree: SourceFileSyntax 226 | 227 | let treeID: UInt 228 | 229 | var refactorableDecls: [RefactorableDecl] 230 | 231 | let slc: SourceLocationConverter 232 | 233 | init(source: String) throws { 234 | self.tree = try SyntaxParser.parse(source: source) 235 | self.treeID = UInt.random(in: .min...(.max)) 236 | self.refactorableDecls = [] 237 | self.slc = SourceLocationConverter(file: "IN_MEMORY_SOURCE", source: source) 238 | } 239 | 240 | } 241 | 242 | /// - Note: 243 | /// Currently does not support private. 244 | /// 245 | /// Private members have their context id, which is hardly to be derived by 246 | /// human rememberable rules. This conflict the design that the global 247 | /// identifier is a simple human readable string. 248 | /// 249 | private class BindingsReader: SyntaxVisitor { 250 | 251 | class Node { 252 | 253 | enum Payload { 254 | case topLevel(children: [Node]) 255 | indirect case variable( 256 | parent: Node, 257 | isStatic: Bool, 258 | children: [Node] 259 | ) 260 | indirect case function( 261 | parent: Node, 262 | isStatic: Bool, 263 | signature: String, 264 | children: [Node] 265 | ) 266 | indirect case binding( 267 | parent: Node, 268 | identifier: String, 269 | type: String?, 270 | children: [Node] 271 | ) 272 | indirect case getter( 273 | parent: Node, 274 | children: [Node] 275 | ) 276 | indirect case setter( 277 | parent: Node, 278 | children: [Node] 279 | ) 280 | indirect case `enum`( 281 | parent: Node, 282 | identifier: String, 283 | children: [Node] 284 | ) 285 | indirect case `struct`( 286 | parent: Node, 287 | identifier: String, 288 | children: [Node] 289 | ) 290 | indirect case `class`( 291 | parent: Node, 292 | identifier: String, 293 | children: [Node] 294 | ) 295 | indirect case `extension`( 296 | parent: Node, 297 | extentedType: String, 298 | whereClause: String?, 299 | children: [Node] 300 | ) 301 | indirect case codeBlock( 302 | parent: Node, 303 | children: [Node] 304 | ) 305 | 306 | } 307 | 308 | private(set) var payload: Payload 309 | 310 | init(payload: Payload) { 311 | self.payload = payload 312 | } 313 | 314 | var parent: Node? { 315 | switch payload { 316 | case .topLevel: 317 | return nil 318 | case .variable(let parent, _, _): 319 | return parent 320 | case .function(let parent, _, _, _): 321 | return parent 322 | case .binding(let parent, _, _, _): 323 | return parent 324 | case .getter(let parent, _): 325 | return parent 326 | case .setter(let parent, _): 327 | return parent 328 | case .enum(let parent, _, _): 329 | return parent 330 | case .struct(let parent, _, _): 331 | return parent 332 | case .class(let parent, _, _): 333 | return parent 334 | case .extension(let parent, _, _, _): 335 | return parent 336 | case .codeBlock(let parent, _): 337 | return parent 338 | } 339 | } 340 | 341 | func addChild(_ node: Node) { 342 | func addChildToChildren(_ children: inout [Node]) { 343 | children.append(node) 344 | } 345 | switch payload { 346 | case .topLevel(var children): 347 | addChildToChildren(&children) 348 | self.payload = .topLevel(children: children) 349 | case .variable(let parent, let isStatic, var children): 350 | addChildToChildren(&children) 351 | self.payload = .variable(parent: parent, isStatic: isStatic, children: children) 352 | case .function(let parent, let isStatic, let signature, var children): 353 | addChildToChildren(&children) 354 | self.payload = .function(parent: parent, isStatic: isStatic, signature: signature, children: children) 355 | case .binding(let parent, let identifier, let type, var children): 356 | addChildToChildren(&children) 357 | self.payload = .binding(parent: parent, identifier: identifier, type: type, children: children) 358 | case .getter(let parent, var children): 359 | addChildToChildren(&children) 360 | self.payload = .getter(parent: parent, children: children) 361 | case .setter(let parent, var children): 362 | addChildToChildren(&children) 363 | self.payload = .setter(parent: parent, children: children) 364 | case .enum(let parent, let identifier, var children): 365 | addChildToChildren(&children) 366 | self.payload = .enum(parent: parent, identifier: identifier, children: children) 367 | case .struct(let parent, let identifier, var children): 368 | addChildToChildren(&children) 369 | self.payload = .struct(parent: parent, identifier: identifier, children: children) 370 | case .class(let parent, let identifier, var children): 371 | addChildToChildren(&children) 372 | self.payload = .class(parent: parent, identifier: identifier, children: children) 373 | case .extension(let parent, let extentedType, let whereClause, var children): 374 | addChildToChildren(&children) 375 | self.payload = .`extension`(parent: parent, extentedType: extentedType, whereClause: whereClause, children: children) 376 | case .codeBlock(let parent, var children): 377 | addChildToChildren(&children) 378 | self.payload = .codeBlock(parent: parent, children: children) 379 | } 380 | } 381 | 382 | var isVariable: Bool { 383 | if case .variable = payload { 384 | return true 385 | } 386 | return false 387 | } 388 | 389 | var isBinding: Bool { 390 | if case .binding = payload { 391 | return true 392 | } 393 | return false 394 | } 395 | 396 | var asBinding: (identifier: String, type: String?) { 397 | guard case let .binding(_, id, ty, _) = payload else { 398 | preconditionFailure() 399 | } 400 | return (id, ty) 401 | } 402 | 403 | func makeBinding() -> (GlobalIdentifier, String?) { 404 | precondition(isBinding) 405 | var all = [self] 406 | 407 | var parentOrNil: Node? = self.parent 408 | while let parent = parentOrNil { 409 | all.append(parent) 410 | parentOrNil = parent.parent 411 | } 412 | 413 | let identifier = all.reversed().compactMap(\.componentDescription).joined(separator: ".") 414 | let type = asBinding.type 415 | return (identifier, type) 416 | } 417 | 418 | var componentDescription: String? { 419 | switch payload { 420 | case .topLevel(_): 421 | return nil 422 | case .variable(_, let isStatic, _): 423 | if isStatic { 424 | return "static" 425 | } else { 426 | return nil 427 | } 428 | case .function(_, let isStatic, let signature, _): 429 | if isStatic { 430 | return "static.\(signature)" 431 | } else { 432 | return signature 433 | } 434 | case .binding(_, let identifier, _, _): 435 | return identifier 436 | case .getter(_, _): 437 | return "getter" 438 | case .setter(_, _): 439 | return "setter" 440 | case .enum(_, let identifier, _): 441 | return identifier 442 | case .struct(_, let identifier, _): 443 | return identifier 444 | case .class(_, let identifier, _): 445 | return identifier 446 | case .extension(_, let extentedType, let whereClause, _): 447 | if let whereClause = whereClause { 448 | return "(extension)\(extentedType)<\(whereClause)>" 449 | } else { 450 | return "(extension)\(extentedType)" 451 | } 452 | case .codeBlock(_, _): 453 | return nil 454 | } 455 | } 456 | 457 | } 458 | 459 | let tree: SourceFileSyntax 460 | 461 | private(set) var bindings: Bindings 462 | 463 | private var hasRead: Bool 464 | 465 | private let rootNode: Node 466 | 467 | private unowned var topNode: Node 468 | 469 | init(tree: SourceFileSyntax) { 470 | let node = Node(payload: .topLevel(children: [])) 471 | self.tree = tree 472 | self.bindings = [:] 473 | self.hasRead = false 474 | self.rootNode = node 475 | self.topNode = node 476 | } 477 | 478 | func readIfNeeded() { 479 | if !hasRead { 480 | read() 481 | hasRead = true 482 | } 483 | } 484 | 485 | private func read() { 486 | walk(tree) 487 | } 488 | 489 | private func push(_ payload: Node.Payload) { 490 | let node = Node(payload: payload) 491 | topNode.addChild(node) 492 | topNode = node 493 | } 494 | 495 | private func pop() { 496 | topNode = topNode.parent! 497 | } 498 | 499 | private func makeBinding() { 500 | let (key, value) = topNode.makeBinding() 501 | bindings[key] = value 502 | } 503 | 504 | override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { 505 | push( 506 | .variable( 507 | parent: topNode, 508 | isStatic: node.modifiers?.containsStatic == true, 509 | children: [] 510 | ) 511 | ) 512 | return super.visit(node) 513 | } 514 | 515 | override func visitPost(_ node: VariableDeclSyntax) { 516 | super.visitPost(node) 517 | pop() 518 | } 519 | 520 | override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { 521 | let signature = node.identifier.simpleDescription + 522 | node.signature.simpleDescription 523 | push( 524 | .function( 525 | parent: topNode, 526 | isStatic: node.modifiers?.containsStatic == true, 527 | signature: signature, 528 | children: [] 529 | ) 530 | ) 531 | return super.visit(node) 532 | } 533 | 534 | override func visitPost(_ node: FunctionDeclSyntax) { 535 | super.visitPost(node) 536 | pop() 537 | } 538 | 539 | override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { 540 | if let idSyntax = node.pattern.as(IdentifierPatternSyntax.self) { 541 | let typeSyntax = node.typeAnnotation?.type.as(SimpleTypeIdentifierSyntax.self) 542 | let id = idSyntax.simpleDescription 543 | let type = typeSyntax?.simpleDescription 544 | push(.binding(parent: topNode, identifier: id, type: type, children: [])) 545 | makeBinding() 546 | } 547 | return super.visit(node) 548 | } 549 | 550 | override func visitPost(_ node: PatternBindingSyntax) { 551 | super.visitPost(node) 552 | if node.pattern.is(IdentifierPatternSyntax.self) { 553 | pop() 554 | } 555 | } 556 | 557 | override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { 558 | push(.struct(parent: topNode, identifier: node.identifier.text, children: [])) 559 | return super.visit(node) 560 | } 561 | 562 | override func visitPost(_ node: StructDeclSyntax) { 563 | super.visitPost(node) 564 | pop() 565 | } 566 | 567 | override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { 568 | push(.`class`(parent: topNode, identifier: node.identifier.text, children: [])) 569 | return super.visit(node) 570 | } 571 | 572 | override func visitPost(_ node: ClassDeclSyntax) { 573 | super.visitPost(node) 574 | pop() 575 | } 576 | 577 | override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { 578 | push(.`enum`(parent: topNode, identifier: node.identifier.text, children: [])) 579 | return super.visit(node) 580 | } 581 | 582 | override func visitPost(_ node: EnumDeclSyntax) { 583 | super.visitPost(node) 584 | pop() 585 | } 586 | 587 | override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { 588 | push( 589 | .extension( 590 | parent: topNode, 591 | extentedType: node.extendedType.simpleDescription, 592 | whereClause: node.genericWhereClause?.simpleDescription, 593 | children: [] 594 | ) 595 | ) 596 | return super.visit(node) 597 | } 598 | 599 | override func visitPost(_ node: ExtensionDeclSyntax) { 600 | super.visitPost(node) 601 | pop() 602 | } 603 | 604 | override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { 605 | if topNode.isVariable { 606 | push(.getter(parent: topNode, children: [])) 607 | } else { 608 | push(.codeBlock(parent: topNode, children: [])) 609 | } 610 | return super.visit(node) 611 | } 612 | 613 | override func visitPost(_ node: CodeBlockSyntax) { 614 | super.visitPost(node) 615 | pop() 616 | } 617 | 618 | } 619 | } 620 | 621 | extension ModifierListSyntax { 622 | 623 | fileprivate var containsStatic: Bool { 624 | self.contains { decl in 625 | decl.firstToken?.tokenKind == .staticKeyword 626 | } 627 | } 628 | 629 | } 630 | 631 | extension SyntaxProtocol { 632 | 633 | var simpleDescription: String { 634 | withoutTrivia().description 635 | } 636 | 637 | } 638 | -------------------------------------------------------------------------------- /COWRewriterTests/Sema/SemaTypeInferTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemaTypeInferTests.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/26/22. 6 | // 7 | 8 | import XCTest 9 | import SwiftSyntax 10 | import SwiftSyntaxParser 11 | import SwiftSyntaxBuilder 12 | 13 | @testable 14 | import COWRewriter 15 | 16 | // TODO: Need to take malformed but parsable AST into consideration? 17 | 18 | class SemaTypeInferTests: SemaTestsBase { 19 | 20 | // MARK: Can Infer Type of Pattern Binding Syntax 21 | 22 | func testPatternBindingWithIntegerLiteral() { 23 | let source = """ 24 | let value1 = 0 25 | var value2 = 0 26 | """ 27 | withSource(source) 28 | .expectTypeChecking("value1", with: "Int") 29 | .expectTypeChecking("value2", with: "Int") 30 | .evaluate(.typeCheck) 31 | } 32 | 33 | func testPatternBindingWithBooleanLiteral() { 34 | let source = """ 35 | let value1 = true 36 | let value2 = false 37 | """ 38 | withSource(source) 39 | .expectTypeChecking("value1", with: "Bool") 40 | .expectTypeChecking("value2", with: "Bool") 41 | .evaluate(.typeCheck) 42 | } 43 | 44 | func testPatternBindingWithFloatingPointLiteral() { 45 | let source = """ 46 | let value1 = 0.1 47 | let value2 = 0.2 48 | """ 49 | withSource(source) 50 | .expectTypeChecking("value1", with: "Double") 51 | .expectTypeChecking("value2", with: "Double") 52 | .evaluate(.typeCheck) 53 | } 54 | 55 | func testPatternBindingWithStringLiteral() { 56 | let source = """ 57 | let value1 = "a" 58 | let value2 = "b" 59 | """ 60 | withSource(source) 61 | .expectTypeChecking("value1", with: "String") 62 | .expectTypeChecking("value2", with: "String") 63 | .evaluate(.typeCheck) 64 | } 65 | 66 | /** 67 | Three kind of cases: 68 | 69 | Initializers whose return type is eventually not clear 70 | Initializers whose return type is eventually an opaque result type 71 | Initializers whose return type is eventually a non-opaque result type 72 | 73 | And an initializer may be: 74 | 75 | - A free function 76 | - A instance member function 77 | - A static member function 78 | - A free variable getter 79 | - A instance variable getter 80 | - A static variable getter 81 | - A instance member closure 82 | - A static member closure 83 | - A type initializer 84 | - An untyped variable 85 | - An untyped static variable 86 | 87 | static/non-static decides scope 88 | 89 | init/func/var/closure decides type recognition pattern 90 | 91 | */ 92 | 93 | func testSemaCanRecognizeTypeFromPatternInitializerOfClearResultType() { 94 | // let source = """ 95 | // // Free function 96 | // func foo() -> Foo { 97 | // Foo() 98 | // } 99 | // 100 | // // Free variable getter 101 | // var fee: Foo { 102 | // Foo() 103 | // } 104 | // 105 | // struct Foo { 106 | // 107 | // // Type initializer 108 | // Foo() {} 109 | // 110 | // // Static member function 111 | // static func make() -> Foo { Foo() } 112 | // 113 | // // Instance member function 114 | // func bar() -> Foo { 115 | // self 116 | // } 117 | // 118 | // // Instance member variable 119 | // var fee: Foo { 120 | // Foo() 121 | // } 122 | // 123 | // // Static member variable 124 | // static var fee: Foo { 125 | // Foo() 126 | // } 127 | // 128 | // // Instance member closure 129 | // let foe = { 130 | // Foo() 131 | // } 132 | // 133 | // // Instance member closure 134 | // static let foe = { 135 | // Foo() 136 | // } 137 | // 138 | // } 139 | // 140 | // let value1 = foo() // Foo 141 | // let value2 = fee // Foo 142 | // let value3 = Foo() // Foo 143 | // let value4 = Foo.make() // Foo 144 | // let value5 = Foo().bar() // Foo 145 | // let value6 = Foo().fee // Foo 146 | // let value7 = Foo.fee // Foo 147 | // let value8 = Foo().foe() // Foo 148 | // let value9 = Foo.foe() // Foo 149 | // """ 150 | 151 | } 152 | 153 | func testSemaCanRecognizeTypeFromPatternInitializerOfOpaqueResultType() { 154 | // let source = """ 155 | // let value1 = foo() 156 | // 157 | // func foo() -> some Foo { 158 | // } 159 | // """ 160 | 161 | } 162 | 163 | func testSemaCanNotRecognizeTypeFromPatternInitializerMissingInSyntaxTree() { 164 | // let source = """ 165 | // let value1 = foo() 166 | // """ 167 | 168 | } 169 | 170 | // MARK: Type Annotations in Memberwise Initializer Can Contribute to Type Inferring 171 | 172 | } 173 | -------------------------------------------------------------------------------- /COWRewriterTests/XCTestCase+COWRewriterTestsAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+COWRewriterTestsAdditions.swift 3 | // COWRewriterTests 4 | // 5 | // Created by WeZZard on 5/22/22. 6 | // 7 | 8 | import XCTest 9 | 10 | extension XCTestCase { 11 | 12 | /// Asserts that the two strings are equal, providing Unix `diff`-style output 13 | /// if they are not. 14 | /// 15 | /// - Parameters: 16 | /// - actual: The actual string. 17 | /// - expected: The expected string. 18 | /// - message: An optional description of the failure. 19 | /// - file: The file in which failure occurred. Defaults to the file name of 20 | /// the test case in which this function was called. 21 | /// - line: The line number on which failure occurred. Defaults to the line 22 | /// number on which this function was called. 23 | internal func XCTAssertStringsEqualWithDiff( 24 | _ actual: String, 25 | _ expected: String, 26 | _ message: String = "", 27 | file: StaticString = #file, 28 | line: UInt = #line 29 | ) { 30 | let actualLines = actual.components(separatedBy: .newlines) 31 | let expectedLines = expected.components(separatedBy: .newlines) 32 | 33 | let difference = actualLines.difference(from: expectedLines) 34 | if difference.isEmpty { return } 35 | 36 | var result = "" 37 | 38 | var insertions = [Int: String]() 39 | var removals = [Int: String]() 40 | 41 | for change in difference { 42 | switch change { 43 | case .insert(let offset, let element, _): 44 | insertions[offset] = element 45 | case .remove(let offset, let element, _): 46 | removals[offset] = element 47 | } 48 | } 49 | 50 | var expectedLine = 0 51 | var actualLine = 0 52 | 53 | while expectedLine < expectedLines.count || actualLine < actualLines.count { 54 | if let removal = removals[expectedLine] { 55 | result += "-\(removal)\n" 56 | expectedLine += 1 57 | } else if let insertion = insertions[actualLine] { 58 | result += "+\(insertion)\n" 59 | actualLine += 1 60 | } else { 61 | result += " \(expectedLines[expectedLine])\n" 62 | expectedLine += 1 63 | actualLine += 1 64 | } 65 | } 66 | 67 | let failureMessage = "Actual output (+) differed from expected output (-):\n\(result)" 68 | let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)" 69 | XCTFail(fullMessage, file: file, line: line) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /COWRewriterUITests/COWRewriterUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // COWRewriterUITests.swift 3 | // COWRewriterUITests 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import XCTest 9 | 10 | class COWRewriterUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /COWRewriterUITests/COWRewriterUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // COWRewriterUITestsLaunchTests.swift 3 | // COWRewriterUITests 4 | // 5 | // Created by WeZZard on 5/21/22. 6 | // 7 | 8 | import XCTest 9 | 10 | class COWRewriterUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 WeZZard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Promotion/Dashboard-Show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/Promotion/Dashboard-Show.png -------------------------------------------------------------------------------- /Promotion/Step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/Promotion/Step-1.png -------------------------------------------------------------------------------- /Promotion/Step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/Promotion/Step-2.png -------------------------------------------------------------------------------- /Promotion/Step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeZZard/COWRewriter/3c9ed7838ec880bafc07941c4c75332e63ec1e79/Promotion/Step-3.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Copy-on-Write Refactorer 2 | 3 | Swift Copy-on-Write Refactorer is a file-level refactorer to refactor a 4 | normal Swift `struct` with **copy-on-write** semantics. This app is 5 | written in SwiftUI and powered by SwiftSyntax and SwiftFormat. 6 | 7 | You can simply drag a Swift source file and drop to the app and then 8 | refactor all available `struct` types in this file. 9 | 10 | > Since this program works with a single Swift source file which means 11 | > type info may be incomplete, this refactorer does not implement type 12 | > inferrence. This means that you need to hand input the type for 13 | > no type annotated `struct` property like `var foo = 0`. 14 | 15 | ## Screenshots 16 | 17 | ![Step1](./Promotion/Step-1.png "Drag and drop your Swift source file") 18 | 19 | ![Step2](./Promotion/Step-2.png "Check out the refactorable types.") 20 | 21 | ![Step3](./Promotion/Step-3.png "Choose the type you want to refactor") 22 | 23 | ![Dashboard Show](./Promotion/Dashboard-Show.png "Icon in Dashboard") 24 | 25 | ## License 26 | MIT 27 | 28 | --------------------------------------------------------------------------------