├── .gitignore ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── StrongSelfRewriter │ └── main.swift └── StrongSelfRewriterCore │ └── StrongSelfRewriter.swift └── Tests ├── LinuxMain.swift └── StrongSelfRewriterTests ├── Sample.swift ├── StrongSelfRewriterTests.swift └── XCTestManifests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwiftPM", 6 | "repositoryURL": "https://github.com/apple/swift-package-manager.git", 7 | "state": { 8 | "branch": "swift-4.2-branch", 9 | "revision": "235aacc514cb81a6881364b0fedcb3dd083228f3", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "SwiftSyntax", 15 | "repositoryURL": "https://github.com/apple/swift-syntax.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "f20c0bcbfb4b07e107c9851350217cb2217f0cb4", 19 | "version": "0.40200.0" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 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: "StrongSelfRewriter", 8 | products: [ 9 | .executable(name:"StrongSelfRewriter", targets: ["StrongSelfRewriter"]), 10 | .library(name: "StrongSelfRewriterCore", targets: ["StrongSelfRewriterCore"]), 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/apple/swift-syntax.git", from: "0.40200.0"), 14 | .package(url: "https://github.com/apple/swift-package-manager.git", .branch("swift-4.2-branch")) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "StrongSelfRewriter", 19 | dependencies: [ 20 | "StrongSelfRewriterCore" 21 | ]), 22 | .target( 23 | name: "StrongSelfRewriterCore", 24 | dependencies: [ 25 | "SwiftSyntax", 26 | "Utility" 27 | ]), 28 | .testTarget( 29 | name: "StrongSelfRewriterTests", 30 | dependencies: ["StrongSelfRewriter"]), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StrongSelfRewriter 2 | StrongSelfRewriter is the tool to replace variable name for `guard let self = self` optional binding code using SwiftSyntax 3 | 4 | ## Usage 5 | ```shell 6 | StrongSelfRewriter Sample.swift 7 | ``` 8 | 9 | Result 10 | ```diff 11 | func execute(completion: () -> Void) { 12 | DispatchQueue.main.async { [weak self] in 13 | - guard let strongSelf = self else { 14 | + guard let self = self else { 15 | return 16 | } 17 | - strongSelf.output(text: "hello") 18 | - print(strongSelf) 19 | + self.output(text: "hello") 20 | + print(self) 21 | } 22 | } 23 | ``` 24 | 25 | Options 26 | ```shell 27 | $ StrongSelfRewriter 28 | OVERVIEW: Replace optional self binding variable name to "self" or specified name by --rewrite option 29 | 30 | USAGE: StrongSelfRewriter 31 | 32 | OPTIONS: 33 | --dryrun, -d Display rewrited code simply 34 | --dump Dump syntax tree 35 | --rewrite Variable name for replacement. Default is self 36 | --help Display available options 37 | 38 | POSITIONAL ARGUMENTS: 39 | path Path to .swift file 40 | ``` 41 | 42 | ## Build 43 | ```shell 44 | git clone 45 | cd 46 | swift build 47 | ``` 48 | Debug run 49 | ```shell 50 | .build/debug/StrongSelfRewriter rewrite Tests/StrongSelfRewriterTests/Sample.swift 51 | ``` 52 | Generate Xcode project 53 | ```shell 54 | swift package generate-xcodeproj 55 | ``` 56 | 57 | Release build 58 | ```shell 59 | swift build -c release 60 | ``` 61 | 62 | ## Thanks 63 | - [SwiftSyntax](https://github.com/apple/swift-syntax) 64 | - [try! Swift Tokyo 2019 - Open Source Swift Workshop](https://www.tryswift.co/events/2019/tokyo/en/#open-source-swift) 65 | -------------------------------------------------------------------------------- /Sources/StrongSelfRewriter/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import StrongSelfRewriterCore 3 | import Utility 4 | import Basic 5 | 6 | let parser = ArgumentParser(commandName: "StrongSelfRewriter", usage: "", overview: "Replace optional self binding variable name to \"self\" or specified name by --rewrite option") 7 | let pathOption = parser.add(positional: "path", kind: String.self, usage: "Path to .swift file") 8 | let dryrunOption = parser.add(option: "--dryrun", shortName: "-d", kind: Bool.self, usage: "Display rewrited code simply") 9 | let dumpOption = parser.add(option: "--dump", kind: Bool.self, usage: "Dump syntax tree") 10 | let rewriteNameOption = parser.add(option: "--rewrite", kind: String.self, usage: "Variable name for using replacement. Default is self") 11 | 12 | do { 13 | let args = try parser.parse(Array(CommandLine.arguments.dropFirst())) 14 | guard let path = args.get(pathOption) else { 15 | fatalError() 16 | } 17 | let fm = FileManager.default 18 | guard fm.fileExists(atPath: path) else { 19 | print("file does not exist: \(path)") 20 | exit(1) 21 | } 22 | let url = URL(fileURLWithPath: path) 23 | let rewrite = args.get(rewriteNameOption) ?? "self" 24 | let rewriter = StrongSelfRewriter(url: url, rewriteName: rewrite) 25 | 26 | let dryrun = args.get(dryrunOption) ?? false 27 | let dump = args.get(dumpOption) ?? false 28 | rewriter.rewrite(dryrun: dryrun, dump: dump) 29 | 30 | } catch { 31 | parser.printUsage(on: stdoutStream) 32 | } 33 | -------------------------------------------------------------------------------- /Sources/StrongSelfRewriterCore/StrongSelfRewriter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StrongSelfRewriter.swift 3 | // Created by kenmaz on 2019/03/23. 4 | // 5 | 6 | import Foundation 7 | import SwiftSyntax 8 | 9 | public class StrongSelfRewriter: SyntaxRewriter { 10 | 11 | public func rewrite(dryrun: Bool = false, dump: Bool = false) { 12 | do { 13 | let src = try SyntaxTreeParser.parse(url) 14 | if dump { 15 | Swift.dump(src) 16 | } 17 | let res = visit(src) 18 | if dryrun { 19 | print(res.description) 20 | } else { 21 | try res.description.write(to: url, atomically: true, encoding: .utf8) 22 | } 23 | } catch { 24 | print(error.localizedDescription) 25 | } 26 | } 27 | 28 | let url: URL 29 | let rewriteName: String 30 | 31 | public init(url: URL, rewriteName: String = "self") { 32 | self.url = url 33 | self.rewriteName = rewriteName 34 | super.init() 35 | } 36 | 37 | public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { 38 | let rewriter = SelfRenameRewriter(rewriteName: rewriteName) 39 | let retNode = rewriter.visit(node) 40 | if let targetName = rewriter.targetName { 41 | let res = SelfRefRewriter(targetName: targetName, rewriteName: rewriteName).visit(retNode) 42 | return res as! ExprSyntax 43 | } else { 44 | return node 45 | } 46 | } 47 | } 48 | 49 | class SelfRenameRewriter: SyntaxRewriter { 50 | var targetName: String? = nil 51 | 52 | let rewriteName: String 53 | init(rewriteName: String) { 54 | self.rewriteName = rewriteName 55 | } 56 | 57 | // guard let xxx = yyy else { .. } 58 | override public func visit(_ node: GuardStmtSyntax) -> StmtSyntax { 59 | let conds = node.conditions 60 | for (i, cond) in conds.enumerated() { 61 | guard 62 | let syntax = cond.condition as? OptionalBindingConditionSyntax, // let xxx = yyy 63 | let rightVal = syntax.initializer.value as? IdentifierExprSyntax, // yyy 64 | rightVal.identifier.text == "self", // yyy == "self" 65 | let leftVal = syntax.pattern as? IdentifierPatternSyntax else { // xxx 66 | return node 67 | } 68 | targetName = leftVal.identifier.text 69 | 70 | let identifier = leftVal.identifier 71 | let newKind = TokenKind.identifier(rewriteName) 72 | let newIdentifier = identifier.withKind(newKind) 73 | let newPattern = leftVal.withIdentifier(newIdentifier) 74 | let newSyntax = syntax.withPattern(newPattern) 75 | let newCond = cond.withCondition(newSyntax) 76 | let newConds = conds.replacing(childAt: i, with: newCond) 77 | let newNode = node.withConditions(newConds) 78 | return newNode 79 | } 80 | return node 81 | } 82 | } 83 | 84 | class SelfRefRewriter: SyntaxRewriter { 85 | let targetName: String 86 | let rewriteName: String 87 | init(targetName: String, rewriteName: String) { 88 | self.targetName = targetName 89 | self.rewriteName = rewriteName 90 | } 91 | 92 | override func visit(_ node: IdentifierExprSyntax) -> ExprSyntax { 93 | let identifier = node.identifier 94 | if identifier.text == targetName { 95 | let newIdentifier = identifier.withKind(TokenKind.identifier(rewriteName)) 96 | return node.withIdentifier(newIdentifier) 97 | } else { 98 | return node 99 | } 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import StrongSelfRewriterTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += StrongSelfRewriterTests.allTests() 7 | XCTMain(tests) -------------------------------------------------------------------------------- /Tests/StrongSelfRewriterTests/Sample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample.swift 3 | // StrongSelfRewriter 4 | // 5 | // Created by kenmaz on 2019/03/23. 6 | // 7 | 8 | import Foundation 9 | 10 | class Sample { 11 | 12 | func execute(completion: () -> Void) { 13 | DispatchQueue.main.async { [weak self] in 14 | guard let strongSelf = self else { 15 | return 16 | } 17 | strongSelf.output(text: "hello") 18 | print(strongSelf) 19 | } 20 | } 21 | 22 | func execute2(completion: () -> Void) { 23 | DispatchQueue.main.async { [weak self] in 24 | guard let this = self else { 25 | return 26 | } 27 | this.output(text: "hello") 28 | print(this) 29 | } 30 | } 31 | 32 | //TODO: support this pattern 33 | // func execute3(completion: () -> Void) { 34 | // DispatchQueue.main.async { [weak self] in 35 | // guard let `self` = self else { 36 | // return 37 | // } 38 | // self.output(text: "hello") 39 | // print(self) 40 | // } 41 | // } 42 | 43 | private func output(text: String) { 44 | print(text) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/StrongSelfRewriterTests/StrongSelfRewriterTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class StrongSelfRewriterTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("StrongSelfRewriter") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Tests/StrongSelfRewriterTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(StrongSelfRewriterTests.allTests), 7 | ] 8 | } 9 | #endif --------------------------------------------------------------------------------