iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0
自我挑戰組

自己的 Leak, 自己抓(swift)系列 第 19

Escaping Closure Detector

  • 分享至 

  • xImage
  •  

繼昨天的判斷目標:

  • @escaping
  • (closure),例:
    • (() -> Void)
  • objc closure(@convention(block))

我們需要一個檢測方式去判斷,某種 type 符合 escaping 的格式


EscapingDetector

一樣是透過老方法,透過實作 visitor 檢查特定語法

但是在執行先做一些手腳

"typealias A = \(code)"

// example
typealias A = Int
typealias A = () -> Void

type 在 parse 過程中順利些

public enum EscapingDetector {
    public static func detectWithTypeAlias(code: String) -> Bool {
        let target = "typealias A = \(code)"
        return detect(code: target)
    }

    public static func detect(code: String) -> Bool {
        let target: String = code
        let source: SourceFileSyntax = Parser.parse(source: target)
        let visitor = TypeEscapeVisitor(viewMode: .sourceAccurate)
        visitor.walk(source)
        return visitor.isEscape
    }
}

internal final class TypeEscapeVisitor: SyntaxVisitor {
    internal var isEscape: Bool = false
  
    internal override func visit(_ node: TypeInitializerClauseSyntax) -> SyntaxVisitorContinueKind {
        return self.find(type: node.value) ?? .skipChildren
    }
    
    internal final func find(type: TypeSyntax) -> SyntaxVisitorContinueKind? {
        
        /// Swift.Optional<(Swift.Error) -> ()>
        if let t = type.as(MemberTypeIdentifierSyntax.self) {
            if let elements = t.genericArgumentClause?.arguments,
               elements.count == 1,
               t.baseType.description == "Swift",
               t.name.description == "Optional" {
                // <(Swift.Error) -> ()> -> ["(Swift.Error)", "()"]
                // <Int>                 -> []
                let parts = elements
                    .first?
                    .withoutTrivia()
                    .description
                    .parseSourkitFunctionTypeName() ?? []
              
                if !parts.isEmpty {
                    self.isEscape = true
                }
                return .skipChildren
            }
        }
        
        /// X?
        if let wrapped: TypeSyntax = type.as(OptionalTypeSyntax.self)?.wrappedType {
            return find(type: wrapped)
        }
        
        /// @escaping Closure
        if let attrs: AttributeListSyntax = type.as(AttributedTypeSyntax.self)?.attributes {
            self.walk(attrs) // -> visit(_ node: AttributeSyntax)
            return .skipChildren
        }
        
        /// (Closure)
        if
            let elements: TupleTypeElementListSyntax = type.as(TupleTypeSyntax.self)?.elements,
            let _ = elements.first?.type.as(FunctionTypeSyntax.self),
            elements.count == 1 {
            self.isEscape = true
            return .skipChildren
        }
        
        return nil
    }
    
    internal override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind {
        if node.attributeName.text == "escaping" {
            self.isEscape = true
        }
        
        return .skipChildren
    }
}

Test

最終再用 test 再次驗證我們的想法

final class EscapingDetectorTests: XCTestCase {
    private let normal = "@escaping () -> Void"
    private let parenthesis = "(() -> Void)"
    private let parenthesisOption = "(() -> Void)?"
    private let other = "Int"
    
    func testNormal() throws {
        let target = self.normal
        XCTAssertTrue(EscapingDetector.detectWithTypeAlias(code: target))
    }
    
    func testParenthesis() throws {
        let target = self.parenthesis
        XCTAssertTrue(EscapingDetector.detectWithTypeAlias(code: target))
    }
    
    func testParenthesisOption() throws {
        let target = self.parenthesisOption
        XCTAssertTrue(EscapingDetector.detectWithTypeAlias(code: target))
    }
    
    func testOther() throws {
        let target = self.other
        XCTAssertFalse(EscapingDetector.detectWithTypeAlias(code: target))
    }
    
    func testNew() throws {
        let target = "let action: @escaping () -> Void"
        XCTAssertTrue(EscapingDetector.detect(code: target))
    }
    
    func testNew2() throws {
        let target = "Swift.Optional<(Swift.Error) -> ()>"
        XCTAssertTrue(EscapingDetector.detectWithTypeAlias(code: target))
    }
    
    func testNew2_2() throws {
        let target = "((Swift.Error) -> ())?"
        XCTAssertTrue(EscapingDetector.detectWithTypeAlias(code: target))
    }
}

上一篇
判斷 Closure 是否是 Escape
下一篇
建立 skip list
系列文
自己的 Leak, 自己抓(swift)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言