├── .gitignore ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── IBOutletRewriter │ ├── Commands │ │ ├── DryRun.swift │ │ ├── Run.swift │ │ └── Version.swift │ └── main.swift └── IBOutletRewriterCore │ ├── Common │ ├── ContextualKeywordKind.swift │ └── Extensions.swift │ ├── SourceFile │ ├── SourceFileParser.swift │ ├── SourceFileParserError.swift │ └── SourceFileWriter.swift │ └── SyntaxRewriter │ └── VariableDeclRewriter.swift ├── Tests ├── IBOutletRewriterCoreTests │ └── VariableDeclRewriterTests.swift ├── IBOutletRewriterTests │ ├── IBOutletRewriterTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift ├── bitrise.yml └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | *.xcodeproj 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots/**/*.png 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yusuke Kita 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build build_release install clean 2 | 3 | BIN_DIR = /usr/local/bin 4 | RELEASE_BUILD_FLAGS= -c release --disable-sandbox 5 | 6 | build: 7 | @swift build 8 | build_release: 9 | @swift build $(RELEASE_BUILD_FLAGS) 10 | 11 | install: build_release 12 | @install -d "$(BIN_DIR)" 13 | @install ".build/release/IBOutletRewriter" "$(BIN_DIR)" 14 | 15 | clean: 16 | @rm -rf .build 17 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Commandant", 6 | "repositoryURL": "https://github.com/Carthage/Commandant.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "07cad52573bad19d95844035bf0b25acddf6b0f6", 10 | "version": "0.15.0" 11 | } 12 | }, 13 | { 14 | "package": "Nimble", 15 | "repositoryURL": "https://github.com/Quick/Nimble.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "e9d769113660769a4d9dd3afb855562c0b7ae7b0", 19 | "version": "7.3.4" 20 | } 21 | }, 22 | { 23 | "package": "Quick", 24 | "repositoryURL": "https://github.com/Quick/Quick.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "f2b5a06440ea87eba1a167cab37bf6496646c52e", 28 | "version": "1.3.4" 29 | } 30 | }, 31 | { 32 | "package": "Result", 33 | "repositoryURL": "https://github.com/antitypical/Result.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "2ca499ba456795616fbc471561ff1d963e6ae160", 37 | "version": "4.1.0" 38 | } 39 | }, 40 | { 41 | "package": "SwiftSyntax", 42 | "repositoryURL": "https://github.com/apple/swift-syntax.git", 43 | "state": { 44 | "branch": "xcode11-beta1", 45 | "revision": "fdcbd4bfea2e833467e16d7962c4110c14382b56", 46 | "version": null 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "IBOutletRewriter", 8 | dependencies: [ 9 | // Dependencies declare other packages that this package depends on. 10 | .package(url: "https://github.com/apple/swift-syntax.git", .revision("xcode11-beta1")), 11 | .package(url: "https://github.com/Carthage/Commandant.git", from: "0.8.0"), 12 | ], 13 | targets: [ 14 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 15 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 16 | .target( 17 | name: "IBOutletRewriter", 18 | dependencies: ["IBOutletRewriterCore", "Commandant"]), 19 | .target( 20 | name: "IBOutletRewriterCore", 21 | dependencies: ["SwiftSyntax"]), 22 | .testTarget( 23 | name: "IBOutletRewriterTests", 24 | dependencies: ["IBOutletRewriter"]), 25 | .testTarget( 26 | name: "IBOutletRewriterCoreTests", 27 | dependencies: ["IBOutletRewriterCore", "SwiftSyntax"]), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IBOutletRewriter 2 | 3 | [![Swift 5.1](https://img.shields.io/badge/swift-5.1-orange.svg?style=flat)](https://swift.org/download/) 4 | [![Build Status](https://app.bitrise.io/app/422d74ce9ce2edf9/status.svg?token=LH-9c1ubBpW33I1Nk3b_Qw)](https://app.bitrise.io/app/422d74ce9ce2edf9) 5 | [![codecov](https://codecov.io/gh/kitasuke/IBOutletRewriter/branch/master/graph/badge.svg)](https://codecov.io/gh/kitasuke/IBOutletRewriter) 6 | 7 | ## Overview 8 | 9 | `@IBOutlet` code formatter using [SwiftSyntax](https://github.com/apple/swift-syntax). 10 | 11 | ## Requirements 12 | 13 | Swift 5.1+ 14 | Xcode 11.0+ beta 15 | 16 | ## How to use 17 | 18 | ### Installation 19 | 20 | Run below command 21 | 22 | ```terminal 23 | $ make install 24 | $ IBOutletRewriter help 25 | ``` 26 | 27 | ### Available Commands 28 | 29 | #### `dry-run --path ` 30 | 31 | Dry-run for rewriting IBOutlet declaration 32 | 33 | #### `help` 34 | 35 | Display general or command-specific help 36 | 37 | #### `run --path ` 38 | 39 | Rewrite IBOutlet declaration 40 | 41 | ## Examples 42 | 43 | ### `private` as default 44 | 45 | ```diff 46 | -@IBOutlet weak var button: UIButton! 47 | +@IBOutlet private weak var button: UIButton! 48 | ``` 49 | 50 | ### `weak` as default 51 | 52 | ```diff 53 | -@IBOutlet private var button: UIButton! 54 | +@IBOutlet private weak var button: UIButton! 55 | ``` 56 | 57 | ### No `private(set)` 58 | 59 | ```diff 60 | -@IBOutlet private(set) weak var button: UIButton! 61 | +@IBOutlet private weak var button: UIButton! 62 | ``` 63 | ## TODOs 64 | 65 | - [ ] Support executing `run` to all files in directory 66 | - [ ] Support yml file for customized configuration 67 | - [ ] Better installation way 68 | 69 | ## Acknowledgements 70 | 71 | - [SwiftRewriter](https://github.com/inamiy/SwiftRewriter) 72 | - [swift-ast-explorer-playground](https://github.com/kishikawakatsumi/swift-ast-explorer-playground) 73 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriter/Commands/DryRun.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Run.swift 3 | // IBOutletRewriter 4 | // 5 | // Created by Yusuke Kita on 03/03/19. 6 | // 7 | 8 | import Foundation 9 | import Commandant 10 | import Result 11 | import IBOutletRewriterCore 12 | 13 | struct DryRunCommand: CommandProtocol { 14 | 15 | typealias Options = DryRunOptions 16 | 17 | let verb = "dry-run" 18 | let function = "Dry-run for rewriting IBOutlet declaration" 19 | 20 | func run(_ options: DryRunOptions) -> Result<(), AnyError> { 21 | 22 | do { 23 | let sourceFileParser = SourceFileParser(pathURL: URL(fileURLWithPath: options.path)) 24 | let sourceFileSyntax = try sourceFileParser.parse() 25 | let modifiedSourceFileSyntax = VariableDeclRewriter().visit(sourceFileSyntax) 26 | print(modifiedSourceFileSyntax) 27 | return .init(value: ()) 28 | } catch let error { 29 | return .init(error: AnyError(error)) 30 | } 31 | } 32 | } 33 | 34 | struct DryRunOptions: OptionsProtocol { 35 | 36 | typealias ClientError = AnyError 37 | 38 | fileprivate let path: String 39 | private init(path: String) { 40 | self.path = path 41 | } 42 | 43 | private static func create(_ path: String) -> DryRunOptions { 44 | return DryRunOptions(path: path) 45 | } 46 | 47 | static func evaluate(_ m: CommandMode) -> Result> { 48 | return create 49 | <*> m <| Option(key: "path", defaultValue: "", usage: "path to dry-run IBOutletRewriter") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriter/Commands/Run.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Run.swift 3 | // IBOutletRewriter 4 | // 5 | // Created by Yusuke Kita on 03/03/19. 6 | // 7 | 8 | import Foundation 9 | import Commandant 10 | import Result 11 | import IBOutletRewriterCore 12 | 13 | struct RunCommand: CommandProtocol { 14 | 15 | typealias Options = RunOptions 16 | 17 | let verb = "run" 18 | let function = "Rewrite IBOutlet declaration" 19 | 20 | func run(_ options: RunOptions) -> Result<(), AnyError> { 21 | 22 | do { 23 | let pathURL = URL(fileURLWithPath: options.path) 24 | let parser = SourceFileParser(pathURL: pathURL) 25 | let syntax = try parser.parse() 26 | let modifiedSyntax = VariableDeclRewriter().visit(syntax) 27 | 28 | let writer = SourceFileWriter(pathURL: pathURL) 29 | try writer.write(modifiedSyntax) 30 | return .init(value: ()) 31 | } catch let error { 32 | return .init(error: AnyError(error)) 33 | } 34 | } 35 | } 36 | 37 | struct RunOptions: OptionsProtocol { 38 | 39 | typealias ClientError = AnyError 40 | 41 | fileprivate let path: String 42 | private init(path: String) { 43 | self.path = path 44 | } 45 | 46 | private static func create(_ path: String) -> RunOptions { 47 | return RunOptions(path: path) 48 | } 49 | 50 | static func evaluate(_ m: CommandMode) -> Result> { 51 | return create 52 | <*> m <| Option(key: "path", defaultValue: "", usage: "path to run IBOutletRewriter") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriter/Commands/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Version.swift 3 | // IBOutletRewriter 4 | // 5 | // Created by Yusuke Kita on 04/11/19. 6 | // 7 | 8 | import Foundation 9 | import Commandant 10 | import Result 11 | 12 | struct VersionCommand: CommandProtocol { 13 | 14 | typealias Options = NoOptions 15 | 16 | let verb = "version" 17 | let function = "Display current version of IBOutletRewriter" 18 | 19 | func run(_ options: Options) -> Result<(), AnyError> { 20 | print("1.0.0") 21 | return .init(value: ()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriter/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // IBOutletRewriter 4 | // 5 | // Created by Yusuke Kita on 03/02/19. 6 | // 7 | 8 | import Foundation 9 | import Result 10 | import Commandant 11 | 12 | let registry = CommandRegistry() 13 | 14 | registry.register(RunCommand()) 15 | registry.register(DryRunCommand()) 16 | registry.register(VersionCommand()) 17 | let helpCommand = HelpCommand(registry: registry) 18 | registry.register(helpCommand) 19 | 20 | registry.main(defaultVerb: helpCommand.verb) { error in 21 | fputs("\(error)\n", stderr) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriterCore/Common/ContextualKeywordKind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextualKeywordKind.swift 3 | // IBOutletRewriterCore 4 | // 5 | // Created by Yusuke Kita on 03/03/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum ContextualKeywordKind: String { 11 | case IBOutlet 12 | case weak 13 | } 14 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriterCore/Common/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // IBOutletRewriterCore 4 | // 5 | // Created by Yusuke Kita on 03/03/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | extension TokenKind { 12 | 13 | static var IBOutletKind: TokenKind { 14 | return .identifier(ContextualKeywordKind.IBOutlet.rawValue) 15 | } 16 | } 17 | 18 | extension Trivia { 19 | 20 | static var noSpace: Trivia { 21 | return .init(pieces: []) 22 | } 23 | 24 | static var oneSpace: Trivia { 25 | return .init(pieces: [.spaces(1)]) 26 | } 27 | } 28 | 29 | extension SyntaxFactory { 30 | static func makePrivateModifier() -> DeclModifierSyntax { 31 | return makeDeclModifier( 32 | name: SyntaxFactory.makePrivateKeyword(leadingTrivia: .noSpace, trailingTrivia: .oneSpace), 33 | detailLeftParen: nil, 34 | detail: nil, 35 | detailRightParen: nil 36 | ) 37 | } 38 | 39 | static func makeWeakModifier() -> DeclModifierSyntax { 40 | return makeDeclModifier( 41 | name: SyntaxFactory.makeIdentifier( 42 | ContextualKeywordKind.weak.rawValue, 43 | leadingTrivia: .noSpace, 44 | trailingTrivia: .oneSpace), 45 | detailLeftParen: nil, 46 | detail: nil, 47 | detailRightParen: nil 48 | ) 49 | } 50 | } 51 | 52 | extension ModifierListSyntax { 53 | 54 | func replacingWithPrivateModifier() -> ModifierListSyntax { 55 | let privateModifier = SyntaxFactory.makePrivateModifier() 56 | 57 | if let publicModifier = self.first(where: { $0.name.tokenKind == .publicKeyword }) { 58 | // public -> private 59 | return self.replacing(childAt: publicModifier.indexInParent, with: privateModifier) 60 | } else if let internalModifier = self.first(where: { $0.name.tokenKind == .internalKeyword }) { 61 | // internal -> private 62 | return self.replacing(childAt: internalModifier.indexInParent, with: privateModifier) 63 | } else if let privateSetModifier = self.first(where: { $0.name.tokenKind == .privateKeyword }), 64 | privateSetModifier.detail?.text == "set" { 65 | // private(set) -> private 66 | return self.replacing(childAt: privateSetModifier.indexInParent, with: privateModifier) 67 | } else if self.contains(where: { [.privateKeyword, .fileprivateKeyword].contains($0.name.tokenKind) }) { 68 | // private 69 | return self 70 | } else { 71 | // -> private 72 | return self.inserting(privateModifier, at: 0) 73 | } 74 | } 75 | 76 | func appendingWeakModifier() -> ModifierListSyntax { 77 | guard self.first(where: { ContextualKeywordKind(rawValue: $0.name.text) == .weak }) == nil else { 78 | return self 79 | } 80 | 81 | let weakModifier = SyntaxFactory.makeWeakModifier() 82 | return self.appending(weakModifier) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriterCore/SourceFile/SourceFileParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceFileParser.swift 3 | // IBOutletRewriterCore 4 | // 5 | // Created by Yusuke Kita on 03/02/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public class SourceFileParser { 12 | 13 | public let pathURL: URL 14 | 15 | public init(pathURL: URL) { 16 | self.pathURL = pathURL 17 | } 18 | 19 | public func parse() throws -> SourceFileSyntax { 20 | return try SyntaxParser.parse(pathURL) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriterCore/SourceFile/SourceFileParserError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceFileParserError.swift 3 | // IBOutletRewriterCore 4 | // 5 | // Created by Yusuke Kita on 03/02/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SourceFileParserError: Error { 11 | case invalidInput 12 | } 13 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriterCore/SourceFile/SourceFileWriter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceFileParser.swift 3 | // IBOutletRewriterCore 4 | // 5 | // Created by Yusuke Kita on 03/02/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public class SourceFileWriter { 12 | 13 | public let pathURL: URL 14 | 15 | public init(pathURL: URL) { 16 | self.pathURL = pathURL 17 | } 18 | 19 | public func write(_ sourceFileSyntax: Syntax) throws { 20 | try sourceFileSyntax.description.write(to: pathURL, atomically: true, encoding: .utf8) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/IBOutletRewriterCore/SyntaxRewriter/VariableDeclRewriter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IBOutletRewriter.swift 3 | // IBOutletRewriterCore 4 | // 5 | // Created by Yusuke Kita on 03/03/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public class VariableDeclRewriter: SyntaxRewriter { 12 | 13 | override public func visit(_ node: VariableDeclSyntax) -> DeclSyntax { 14 | // ignore if no @IBOutlet 15 | guard let attributes = node.attributes, 16 | attributes.contains(where: { 17 | guard let attribute = $0 as? AttributeSyntax else { return false } 18 | return attribute.attributeName.tokenKind == .IBOutletKind 19 | }) else { 20 | return super.visit(node) 21 | } 22 | 23 | let newModifiers = (node.modifiers ?? SyntaxFactory.makeModifierList([])) 24 | .replacingWithPrivateModifier() 25 | .appendingWeakModifier() 26 | 27 | let newNode = node.withModifiers(newModifiers) 28 | 29 | return super.visit(newNode) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Tests/IBOutletRewriterCoreTests/VariableDeclRewriterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VariableDeclRewriterTests.swift 3 | // IBOutletRewriterCoreTests 4 | // 5 | // Created by Yusuke Kita on 03/03/19. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | @testable import IBOutletRewriterCore 11 | 12 | final class VariableDeclRewriterTests: XCTestCase { 13 | 14 | func testRewriteVariableDecl() { 15 | let testFixture: [(inputs: [String], expected: String)] = [ 16 | ( 17 | // to private 18 | inputs: [ 19 | """ 20 | class View { 21 | @IBOutlet var view: UIView! 22 | } 23 | """, 24 | """ 25 | class View { 26 | @IBOutlet internal var view: UIView! 27 | } 28 | """, 29 | """ 30 | class View { 31 | @IBOutlet private var view: UIView! 32 | } 33 | """, 34 | """ 35 | class View { 36 | @IBOutlet private(set) var view: UIView! 37 | } 38 | """, 39 | """ 40 | class View { 41 | @IBOutlet public var view: UIView! 42 | } 43 | """, 44 | """ 45 | class View { 46 | @IBOutlet weak var view: UIView! 47 | } 48 | """, 49 | """ 50 | class View { 51 | @IBOutlet internal weak var view: UIView! 52 | } 53 | """, 54 | """ 55 | class View { 56 | @IBOutlet private weak var view: UIView! 57 | } 58 | """, 59 | """ 60 | class View { 61 | @IBOutlet private(set) weak var view: UIView! 62 | } 63 | """, 64 | """ 65 | class View { 66 | @IBOutlet public weak var view: UIView! 67 | } 68 | """, 69 | ], 70 | expected: 71 | """ 72 | class View { 73 | @IBOutlet private weak var view: UIView! 74 | } 75 | """ 76 | ), 77 | ( 78 | // to fileprivate 79 | inputs: [ 80 | """ 81 | class View { 82 | @IBOutlet fileprivate var view: UIView! 83 | } 84 | """, 85 | """ 86 | class View { 87 | @IBOutlet fileprivate weak var view: UIView! 88 | } 89 | """ 90 | ], 91 | expected: 92 | """ 93 | class View { 94 | @IBOutlet fileprivate weak var view: UIView! 95 | } 96 | """ 97 | ) 98 | ] 99 | 100 | testFixture.forEach { inputs, expected in 101 | inputs.forEach { input in 102 | let filePathURL = createSourceFile(from: input) 103 | defer { 104 | delete(filePathURL: filePathURL) 105 | } 106 | 107 | do { 108 | let parser = SourceFileParser(pathURL: filePathURL) 109 | let sourceFileSyntax = try parser.parse() 110 | let output = VariableDeclRewriter().visit(sourceFileSyntax) 111 | XCTAssertEqual(output.description, expected) 112 | } catch let error { 113 | fatalError("error: \(error)") 114 | } 115 | } 116 | } 117 | } 118 | 119 | private func createSourceFile(from input: String) -> URL { 120 | let url = URL(fileURLWithPath: NSTemporaryDirectory()) 121 | .appendingPathComponent(UUID().uuidString) 122 | .appendingPathExtension("swift") 123 | try! input.write(to: url, atomically: true, encoding: .utf8) 124 | 125 | return url 126 | } 127 | 128 | private func delete(filePathURL url: URL) { 129 | try! FileManager().removeItem(at: url) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Tests/IBOutletRewriterTests/IBOutletRewriterTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class IBOutletRewriterTests: XCTestCase { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /Tests/IBOutletRewriterTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(IBOutletRewriterTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftOptimizerTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftOptimizerTests.allTests() 7 | XCTMain(tests) -------------------------------------------------------------------------------- /bitrise.yml: -------------------------------------------------------------------------------- 1 | --- 2 | format_version: '6' 3 | default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git 4 | project_type: other 5 | trigger_map: 6 | - push_branch: master 7 | workflow: primary 8 | - pull_request_source_branch: "*" 9 | workflow: primary 10 | - tag: "*" 11 | workflow: primary 12 | workflows: 13 | primary: 14 | steps: 15 | - activate-ssh-key@4.0.3: 16 | run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' 17 | - git-clone@4.0.14: {} 18 | - swift-package-manager-build-for-mac@0.0.1: 19 | inputs: 20 | - build_tests: 'yes' 21 | - swift-package-manager-test-for-mac@0.0.1: 22 | inputs: 23 | - is_parallel: 'yes' 24 | - is_skip_build: 'yes' 25 | - swift-package-manager-xcodeproj-for-mac@0.0.1: 26 | inputs: 27 | - enable_code_coverage: 'yes' 28 | - xcode-test-mac@1.2.1: 29 | inputs: 30 | - is_clean_build: 'no' 31 | - output_tool: xcodebuild 32 | - generate_code_coverage_files: 'yes' 33 | - codecov@1.1.5: 34 | inputs: 35 | - CODECOV_TOKEN: "$BITRISE_CODECOV_TOKEN" 36 | - deploy-to-bitrise-io@1.3.19: {} 37 | app: 38 | envs: 39 | - opts: 40 | is_expand: false 41 | BITRISE_SCHEME: IBOutletRewriter 42 | - opts: 43 | is_expand: false 44 | BITRISE_PROJECT_PATH: IBOutletRewriter.xcodeproj 45 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "70...100" 9 | 10 | status: 11 | project: yes 12 | patch: yes 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | 23 | comment: 24 | layout: "header, diff" 25 | behavior: default 26 | require_changes: no 27 | --------------------------------------------------------------------------------