Skip to content

Commit

Permalink
visitor detect only provide LeakResult, move report part to Command
Browse files Browse the repository at this point in the history
  • Loading branch information
yume190 committed Sep 7, 2023
1 parent caa988b commit f7fe002
Show file tree
Hide file tree
Showing 10 changed files with 585 additions and 549 deletions.
239 changes: 136 additions & 103 deletions Sources/LeakDetect/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,127 +8,160 @@
import ArgumentParser
import Foundation
import LeakDetectKit
import Rainbow
import SKClient
import SourceKittenFramework

struct Command: AsyncParsableCommand {
static var configuration: CommandConfiguration = .init(
abstract: "A Tool to Detect Potential Leaks",
discussion: """
# Example:
git clone https://github.com/antranapp/LeakDetector
cd LeakDetector
static var configuration: CommandConfiguration = .init(
abstract: "A Tool to Detect Potential Leaks",
discussion: """
# Example:
git clone https://github.com/antranapp/LeakDetector
cd LeakDetector
leakDetect --module LeakDetectorDemo --file LeakDetectorDemo.xcworkspace
""",
version: "0.0.4"
)
leakDetect --module LeakDetectorDemo --file LeakDetectorDemo.xcworkspace
""",
version: "0.0.4"
)

@Flag(name: [.customLong("verbose", withSingleDash: false), .short], help: "verbose")
var verbose: Bool = false
@Flag(name: [.customLong("verbose", withSingleDash: false), .short], help: "verbose")
var verbose: Bool = false

@Option(name: [.customLong("reporter", withSingleDash: false)], help: "[\(Reporter.all)]")
var reporter: Reporter = .vscode
@Option(name: [.customLong("reporter", withSingleDash: false)], help: "[\(Reporter.all)]")
var reporter: Reporter = .vscode

@Option(name: [.customLong("sdk", withSingleDash: false)], help: "[\(SDK.all)]")
var sdk: SDK = .iphonesimulator
@Option(name: [.customLong("sdk", withSingleDash: false)], help: "[\(SDK.all)]")
var sdk: SDK = .iphonesimulator

@Option(name: [.customLong("targetType", withSingleDash: false)], help: "[\(TargetType.all)]")
var targetType: TargetType = .auto
@Option(name: [.customLong("targetType", withSingleDash: false)], help: "[\(TargetType.all)]")
var targetType: TargetType = .auto

@Option(name: [.customLong("module", withSingleDash: false)], help: "Name of Swift module to document (can't be used with `--targetType singleFile`)")
var moduleName = ""
@Option(name: [.customLong("module", withSingleDash: false)], help: "Name of Swift module to document (can't be used with `--targetType singleFile`)")
var moduleName = ""

@Option(name: [.customLong("file", withSingleDash: false)], help: "xxx.xcworkspace/xxx.xcodeproj/xxx.swift")
var file: String
var path: String {
URL(fileURLWithPath: file).path
@Option(name: [.customLong("file", withSingleDash: false)], help: "xxx.xcworkspace/xxx.xcodeproj/xxx.swift")
var file: String
var path: String {
URL(fileURLWithPath: file).path
}

var base: String {
let _base = path.removeSuffix(file)

if _base.isEmpty {
return URL(fileURLWithPath: file).deletingLastPathComponent().path
} else {
return _base
}

var base: String {
let _base = path.removeSuffix(file)

if _base.isEmpty {
return URL(fileURLWithPath: file).deletingLastPathComponent().path
} else {
return _base
}
}

@Argument(help: "Arguments passed to `xcodebuild` or `swift build`")
var arguments: [String] = []

private var module: Module? {
let moduleName = self.moduleName.isEmpty ? nil : self.moduleName

switch targetType.detect(path) {
case .spm:
return Module(spmArguments: arguments, spmName: moduleName, inPath: path)
case .singleFile:
return nil
case .xcodeproj:
let newArgs: [String] = [
"-project",
path,
"-scheme",
self.moduleName,
]
return Module(xcodeBuildArguments: arguments + newArgs, name: moduleName)
case .xcworkspace:
let newArgs: [String] = [
"-workspace",
path,
"-scheme",
self.moduleName,
]
return Module(xcodeBuildArguments: arguments + newArgs, name: moduleName)
case .auto:
return nil
}
}

@Argument(help: "Arguments passed to `xcodebuild` or `swift build`")
var arguments: [String] = []

private var module: Module? {
let moduleName = self.moduleName.isEmpty ? nil : self.moduleName

switch targetType.detect(path) {
case .spm:
return Module(spmArguments: arguments, spmName: moduleName, inPath: path)
case .singleFile:
return nil
case .xcodeproj:
let newArgs: [String] = [
"-project",
path,
"-scheme",
self.moduleName,
]
return Module(xcodeBuildArguments: arguments + newArgs, name: moduleName)
case .xcworkspace:
let newArgs: [String] = [
"-workspace",
path,
"-scheme",
self.moduleName,
]
return Module(xcodeBuildArguments: arguments + newArgs, name: moduleName)
case .auto:
return nil
}
private func newReporter() -> (Reporter, GithubAtionReporter?) {
switch reporter {
case .xcode: fallthrough
case .vscode:
return (reporter, nil)
case .custom:
let githubAction = GithubAtionReporter(base: base)
let reporter = Reporter.custom { [weak githubAction] location, _ in
githubAction?.add(location)
}
return (reporter, githubAction)
}
private func newReporter() -> (Reporter, GithubAtionReporter?) {
switch reporter {
case .xcode: fallthrough
case .vscode:
return (reporter, nil)
case .custom:
let githubAction = GithubAtionReporter(base: base)
let reporter = Reporter.custom { [weak githubAction] location, _ in
githubAction?.add(location)
}
return (reporter, githubAction)
}
}

mutating func run() async throws {
let (reporter, githubAction) = newReporter()

if case .singleFile = targetType.detect(path) {
let results = try Pipeline(path, arguments + [path] + sdk.args)
.detect()
report(reporter, results)

try await githubAction?.call()
summery(results.count)
return
}

mutating func run() async throws {
let (reporter, githubAction) = newReporter()

if case .singleFile = targetType.detect(path) {
let results = try SingleFilePipeline(path, arguments + [path] + sdk.args)
.detect(reporter, verbose)

try await githubAction?.call()
summery(results.count)
return
}

guard let module = module else {
print("Can't create module")
return
}

let count = try Pipeline.detect(module, reporter, verbose)
try await githubAction?.call()
summery(count)

guard let module = module else {
print("Can't create module")
return
}
}

let results = try Pipeline.parse(module)
let count = try report(reporter, results)
try await githubAction?.call()
summery(count)
}

fileprivate func summery(_ leakCount: Int) {
if leakCount == 0 {
print("Congratulation no leak found".green)
} else {
print("Found \(leakCount) leaks".red)
private func report(
_ reporter: Reporter,
_ results: [(index: Int, filePath: String, pipeline: Pipeline)]
) throws -> Int {
var leakCount = 0
let all: Int = results.count
for (index, filePath, pipeline) in results {
if verbose {
let title = "[SCAN \(index + 1)/\(all)]:".applyingCodes(Color.yellow, Style.bold)
print("\(title) \(filePath)")
}
let results = try pipeline.detect()
report(reporter, results)
leakCount += results.count
}

return leakCount
}

private func report(
_ reporter: Reporter,
_ results: [LeakResult]
) {
results.forEach { result in
reporter.report(result)
if verbose, !result.verbose.isEmpty {
print(result.verbose)
}
}
}
}

private func summery(_ leakCount: Int) {
if leakCount == 0 {
print("Congratulation no leak found".green)
} else {
print("Found \(leakCount) leaks".red)
}
}
77 changes: 41 additions & 36 deletions Sources/LeakDetectKit/Capture/IdentifierVisitor.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// IdentifierVisitor.swift
//
//
//
// Created by Yume on 2022/5/20.
//
Expand All @@ -12,46 +12,51 @@ import SwiftSyntax
/// in function call `ID1(x, ID2, x.x(ID3), x.x {ID4})`
/// in closure `{ [ID1] ID2 in ID3}`
final class IdentifierVisitor: SyntaxVisitor {
lazy var ids: [IdentifierExprSyntax] = []
unowned let parentVisitor: LeakVisitor
init(parentVisitor: LeakVisitor) {
self.parentVisitor = parentVisitor
super.init(viewMode: .sourceAccurate)
}
lazy var ids: [IdentifierExprSyntax] = []
unowned let parentVisitor: LeakVisitor
init(parentVisitor: LeakVisitor) {
self.parentVisitor = parentVisitor
super.init(viewMode: .sourceAccurate)
}

/// xxx(...)
final override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}
/// xxx(...)
override final func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}

/// { [...] _ in ... }
final override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}
/// { [...] _ in ... }
override final func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}

/// a?.b?.c
/// `a`
final override func visit(_ node: OptionalChainingExprSyntax) -> SyntaxVisitorContinueKind {
if let base = node.expression.firstBase {
ids.append(base)
}

return .skipChildren
/// a?.b?.c
/// `a`
override final func visit(_ node: OptionalChainingExprSyntax) -> SyntaxVisitorContinueKind {
if let base = node.expression.firstBase {
ids.append(base)
}

/// a.b.c
/// `a`
final override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
if let base = node.base?.firstBase {
ids.append(base)
}

return .skipChildren
}
return .skipChildren
}

/// `a`
final override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
ids.append(node)
return .skipChildren
/// a.b.c
/// `a`
override final func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
if let base = node.base?.firstBase {
ids.append(base)
}

return .skipChildren
}

/// `a`
override final func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
ids.append(node)
return .skipChildren
}

override final func visit(_ node: ClosureCaptureItemSyntax) -> SyntaxVisitorContinueKind {
walk(node.expression)
return .skipChildren
}
}
Loading

0 comments on commit f7fe002

Please sign in to comment.