CodePecker is a tool to detect unused Swift code.

Related tags

Tools Pecker
Overview

Notice: The "dyld: Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib" or missing warnings are caused by a SwiftSyntax issue, SwiftSyntax with Swift 5.1.

Pecker

Pecker detects unused code. It's based on IndexStoreDB and SwiftSyntax.

Chinese Readme: 中文版readme.

Motivation

As your Swift project codebase grows, it is hard to locate unused code. You need to tell if some constructs are still in used. pecker does this job for you, easy and accurate.

Features

pecker detects the following Swift constructs:

  1. class
  2. struct
  3. enum
  4. protocol
  5. func
  6. typealias
  7. operator

Installation

There're more than one way to install pecker.

Using Homebrew

$ brew install woshiccm/homebrew-tap/pecker

Using CocoaPods:

pod 'Pecker'

This will download the pecker binaries and dependencies in Pods/ during your next run of pod install and will allow you to invoke it via ${PODS_ROOT}/Pecker/bin/pecker in your script build phases.

This is the recommended way to install a specific version of pecker since it supports installing a specific stable version rather than head version.

Using Mint:

mint install woshiccm/Pecker

Compiling from source

$ git clone https://github.com/woshiccm/Pecker.git
$ cd Pecker
$ make install

With that installed and in the bin folder, now it's ready to serve.

Usage

Xcode

Integrate pecker into an Xcode scheme to get warnings and errors displayed in the IDE. Just add a new "Run Script Phase" with:

if which pecker >/dev/null; then
  pecker
else
  echo "warning: Pecker not installed, download from https://github.com/woshiccm/Pecker"
fi

Alternatively, if you've installed Pecker via CocoaPods the script should look like this:

${PODS_ROOT}/Pecker/bin/pecker

Terminal

Note:

  1. In terminal, since project index path can't be retrieved automatically there, so you need to set index path through -i/--index-store-path
  2. Need to set reporter as json and set output_file, the path can be both relative and absolute. If output_file is not specified, it defaults to be pecker.result.json in your project.

For example:

In .pecker.yml, the configuration is:

reporter: "json"
output_file: pecker.result.json

In terminal, you input:

$ pecker --path /Users/ming/Desktop/Testttt -i /Users/ming/Library/Developer/Xcode/DerivedData/Testttt-aohluxvofrwtfagozexmpeifvryf/Index/DataStore

Command Line

pecker [OPTIONS]
  • -v/--version: Prints the pecker version and exits.
  • --config: The custom path for configuration yaml file.
  • -i/--index-store-path: The Index path of your project, if unspecified, the default is ~Library/Developer/Xcode/DerivedData/{your project}/Index/DataStore.

Run pecker in the project target to detect. Project will search Swift files recursively.

Rules

Current only 5 rules are included in Pecker, They are skip_public, xctest, attributes, xml, comment. You can add them to disabled_rules if you don't need it. You can also check Source/PeckerKit/Rules directory to see their implementation.

skip_public

This rule means skip detect public class, struct, function, etc. Usually the public code is provided for other users, so it is difficult to determine whether it is used. So we don't detect it by default. But in some cases, such as using submodule to organize code, you need to detect public code, you can add it to disabled_rules.

xctest

XCTest is special, we stipulate that ignore classes inherited from XCTestCase and functions of this class that hasPrefix "test" and do not contain parameters.

class ExampleUITests: XCTestCase {

    func testExample() { //used
    }

    func test(name: String) { // unused
    }
    
    func get() { // unused
    }
}

attributes

If a Declaration contains the attribute in BlackListAttribute, skip. Such as IBAction, we are continuously collecting, if you find new cases, please let us know.

@IBAction func buttonTap(_ sender: Any) { // used       
}

XML

If code is being used in .xib or storyboard, we say it's in use.

comment

Code can be ignored with a comment inside a source file with the following format:

  • Ignore specified code
// pecker:ignore 

For example:

// pecker:ignore
class TestCommentObject { // skip
    
    // pecker:ignore
    func test1() { // skip
    }
    
    func test2() { // unused
    }
}
  • Ignore all symbols under scope
// pecker:ignore all

For example:

// pecker:ignore all
class TestCommentObject { // skip
    
    func test1() { // skip
    }
    
    struct SubClass { // skip
        
        func test2() { // skip
        }
    }
}

Other rules

These rules are used by default, you cannot configure them.

override

Skip declarations that override another. This works for both subclass overrides & protocol extension overrides.

protocol ExampleProtocol {
	func test() // used
}

class Example: ExampleProtocol {
    func test() { // used
    }
}

class Animal {
    func run() {  // used
    }
}

class Dog: Animal {
    override func run() { // used
    }
}

extensions

Referenced elsewhere means used, except for extensions.

class UnusedExample { // unused
    
}

extension UnusedExample {
    
}

Configuration

This is optional, will use default, if unspecified. Configure pecker by adding a .pecker.yml file from the directory you'll run pecker from. The following parameters can be configured:

Rule inclusion:

  • disabled_rules: Disable rules from the default enabled set.

Reporter inclusion:

  • xcode: Warnings displayed in the IDE.

  • json: you can set path by output_file, and the path can be both relative and absolute path, if unspecified, the default is pecker.result.json in current project directory.

reporter: "xcode"

disabled_rules:
  - skip_public

included: # paths to include during detecting. `--path` is ignored if present.
  - ./
  
excluded: # paths to ignore during detecting. Takes precedence over `included`.
  - Carthage
  - Pods

excludedGroupName: # names of group to ignore during detecting.
  - SwiftPeckerTestUITests

blacklist_files: # files to ignore during detecting, only need to add file name, the file extension default is swift.
  - HomeViewController

blacklist_symbols: # symbols to ignore during detecting, contains class, struct, enum, etc.
  - AppDelegate
  - viewDidLoad

blacklist_superclass: # all the class inherit from class specified in the list will ignore
  - UITableViewCell

# If output_file is not specified, the defaults to be pecker.result.json in your project

output_file: pecker.result.jsonthe path can be both relative and absolute.

Contributions and support

pecker is developed completely in the open.

Any contributing and pull requests are warmly welcome. If you are interested in developing pecker, submit ideas and submit pull requests!

Licence

pecker is released under the MIT License.

Comments
  • Pecker seems to be broken in Xcode 11.4

    Pecker seems to be broken in Xcode 11.4

    I'm integrating Pecker via Run Script Phase. It stopped working as soon as I upgraded Xcode to version 11.4.

    Pecker version: 0.1.0

    Error: Command PhaseScriptExecution failed with a nonzero exit code

    There isn't much info besides this:

    bash PhaseScriptExecution Run\ Pecker /Users/fernando.fernandes/Library/Developer/Xcode/DerivedData/TEST-awvaoxeibgfjlqccvsjwuqsmgoij/Build/Intermediates.noindex/TEST.build/Debug-iphoneos/TEST.build/Script-0765326C23FF1DBB00BB6B45.sh (in target 'TEST' from project 'TEST') cd /Users/fernando.fernandes/Downloads/source/backslash-f/TEST/Xcode/TEST /bin/sh -c /Users/fernando.fernandes/Library/Developer/Xcode/DerivedData/TEST-awvaoxeibgfjlqccvsjwuqsmgoij/Build/Intermediates.noindex/TEST.build/Debug-iphoneos/TEST.build/Script-0765326C23FF1DBB00BB6B45.sh

    Please advise, thanks.

    opened by backslash-f 18
  • dyld: Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib

    dyld: Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib

    Environment

    • Xcode11.2.1
    • Swift5.1.2

    When I was trying to debug pecker with xcode, the following error log was output, so it couldn't be executed

    dyld: Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib
    
    opened by funzin 11
  • Request: Cocoapods Support

    Request: Cocoapods Support

    We currently install Swiftlint with Cocoapods, so it would be really nice to have Pecker support for Cocoapods as well. Currently the install process requires manually cloning the project, so it won't get installed automatically when new developers clone our repo. Another option could be to integrate the project as a Swift package - that technically might work now? I'm not sure how to integrate a Swift package for development and not build it into the app, though

    opened by noahsark769 9
  • False Positive: Subclasses which implement optional protocol methods

    False Positive: Subclasses which implement optional protocol methods

    Consider the following code:

    import Foundation
    
    @objc protocol ProtocolWithOptionalMethods: AnyObject {
        func a()
        @objc optional func b()
    }
    
    class Superclass: NSObject, ProtocolWithOptionalMethods {
        func a() {
            print("a")
        }
    }
    
    class Subclass: Superclass {
        func b() {
            print("b")
        }
    }
    

    Currently, Pecker reports function b() was never used; consider removing it, but b is in fact used as an implementation of the optional method. I put together an example project to demonstrate this here: https://github.com/noahsark769/NGPeckerExamples/blob/master/NGPeckerExamples/OptionalFunctionFromSuperclass.swift#L23

    opened by noahsark769 5
  • Warning for Methods in Protocol when satisfying conformance in extension

    Warning for Methods in Protocol when satisfying conformance in extension

    protocol Protocol { var variable: Bool { get } func function(parameter: String?) -> String? }

    Implementing this Protocol in a class and satisfying conformance in extension works for variable but not for function.

    class Class: Protocol { }

    extension Class { var variable: Bool { // no warning return "Temp" }

    func function (parameter: String?) -> { // warning return parameter } }

    opened by RalfK92 4
  • Custom path of configuration file

    Custom path of configuration file

    Hi,

    First of all, thanks for great tool. Amazing job 💪

    I found one missed thing that is nice to have. It is custom path for configuration yaml file. For example, SwiftLint allows it in the following way:

    ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml

    Does it make sense for you? Could you, please, provide something similar?

    opened by oleghnidets 3
  • False Positive: SwiftUI Previews

    False Positive: SwiftUI Previews

    By default, SwiftUI generates a struct which conforms to PreviewProvider, e.g.:

    import SwiftUI
    
    struct SettingsView: View {
        var body: some View {
            ...
        }
    }
    
    #if DEBUG
    struct SettingsView_Previews: PreviewProvider {
        static var previews: some View {
            SettingsView()
        }
    }
    #endif
    

    In the default configuration, pecker reports SettingsView_Previews as unused. It would be good to add a rule to ignore structs which conform to PreviewProvider.

    opened by noahsark769 3
  • Request: Blacklist @objc methods

    Request: Blacklist @objc methods

    Since Pecker reports Swift methods that are only used by Obj-C code as unused, it would be nice to have an option to remove methods/properties annotated with @objc from reporting.

    opened by noahsark769 3
  • Request: Blacklist positives with a comment

    Request: Blacklist positives with a comment

    We have a few true positives in our codebase which we'd like to keep around since they're not used yet but will soon be used. It would be nice if we could disable the pecker checker with a comment, similar to Swiftlint. Something like:

    func myUnsusedFunction() { // pecker:ignore
    }
    
    // or:
    
    // pecker:ignore:next
    func myUnusedFunction() {
      print("Blah")
    }
    
    opened by noahsark769 3
  • Request: customizable SuperClassRule

    Request: customizable SuperClassRule

    We use Quick for testing, which means that our tests inherit from QuickSpec. Since this isn't supported out of the box, it would be nice for us to be able to ignore all subclasses of Quickspec by default.

    It seems the best way would be to make the SuperClassRule customizable via .pecker.yml for this.

    opened by noahsark769 2
  • False Positive: Subclasses of subclasses of XCTestCase

    False Positive: Subclasses of subclasses of XCTestCase

    If you have a subclass of XCTestCase (e.g. a BaseTestCase where you put your generic testing utilities), Pecker currently reports tests that inherit from this subclass these as unused. For example:

    import XCTest
    
    class BaseTestCase: XCTestCase {
    
    }
    
    extension BaseTestCase {
        func a() {
            print("a")
        }
    }
    
    class TestCase: BaseTestCase { // currently reported as unused
        func test() { // currently reported as unused
            self.a()
        }
    }
    

    Working example in repo: https://github.com/noahsark769/NGPeckerExamples/blob/master/NGPeckerExamplesTests/XCTestSuperclassMethodUsedBySubclass.swift#L11

    opened by noahsark769 2
  • Failed to compile on macOS 11.5.2 (Intel MacBook Pro)

    Failed to compile on macOS 11.5.2 (Intel MacBook Pro)

    Installed manually and run make install.

    Error message as below

    /Pecker/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:32: warning: initialization of 'UnsafeMutablePointer<yaml_version_directive_t>' (aka 'UnsafeMutablePointer<yaml_version_directive_s>') results in a dangling pointer
                versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    /Users/tieda/Desktop/Pecker/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:53: note: implicit argument conversion from 'yaml_version_directive_t' (aka 'yaml_version_directive_s') to 'UnsafeMutablePointer<yaml_version_directive_t>' (aka 'UnsafeMutablePointer<yaml_version_directive_s>') produces a pointer valid only for the duration of the call to 'init(_:)'
                versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                                        ^~~~~~~~~~~~~~~~~~~~~~
    /Users/tieda/Desktop/Pecker/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:53: note: use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope
                versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                                        ^
    /Users/tieda/Desktop/Pecker/.build/checkouts/swift-syntax/Sources/SwiftSyntax/RawSyntax.swift:120:46: error: value of type 'CSyntaxNode' (aka 'swiftparse_syntax_node_t') has no member 'range'
        let textSize = hasCustomText ? Int(cnode.range.length) : 0
                                           ~~~~~ ^~~~~
    /Users/tieda/Desktop/Pecker/.build/checkouts/swift-syntax/Sources/SwiftSyntax/RawSyntax.swift:153:35: error: value of type 'CSyntaxNode' (aka 'swiftparse_syntax_node_t') has no member 'range'
          let startOffset = Int(cnode.range.offset)
                                ~~~~~ ^~~~~
    /Users/tieda/Desktop/Pecker/.build/checkouts/swift-syntax/Sources/SwiftSyntax/RawSyntax.swift:157:55: error: value of type 'CSyntaxNode' (aka 'swiftparse_syntax_node_t') has no member 'range'
          let end = utf8.index(begin, offsetBy: Int(cnode.range.length))
                                                    ~~~~~ ^~~~~
    /Users/tieda/Desktop/Pecker/.build/checkouts/swift-syntax/Sources/SwiftSyntax/RawSyntax.swift:873:32: error: value of type 'CSyntaxNode' (aka 'swiftparse_syntax_node_t') has no member 'range'
        let byteLength = Int(cnode.range.length)
                             ~~~~~ ^~~~~
    /Users/tieda/Desktop/Pecker/.build/checkouts/swift-syntax/Sources/SwiftSyntax/SyntaxParser.swift:195:57: error: missing argument for parameter #3 in call
        let c_top = swiftparse_parse_string(c_parser, source)
                                                            ^
                                                            , <#Int#>
    _InternalSwiftSyntaxParser.swiftparse_parse_string:1:13: note: 'swiftparse_parse_string' declared here
    public func swiftparse_parse_string(_: swiftparse_parser_t!, _ source: UnsafePointer<CChar>!, _ len: Int) -> swiftparse_client_node_t!
                ^
    [101/104] Compiling SwiftSyntax AbsolutePosition.swift
    make: *** [build] Error 1
    

    Thanks in advance for looking into this :)

    opened by weitieda 1
  • Can't compile on Apple Silicone M1

    Can't compile on Apple Silicone M1

    /redacted/Pecker/.build/checkouts/swift-syntax/Sources/SwiftSyntax/SyntaxParser.swift:195:57: error: missing argument for parameter #3 in call
        let c_top = swiftparse_parse_string(c_parser, source)
                                                            ^
                                                            , <#Int#>
    _InternalSwiftSyntaxParser.swiftparse_parse_string:1:13: note: 'swiftparse_parse_string' declared here
    public func swiftparse_parse_string(_: swiftparse_parser_t!, _ source: UnsafePointer<CChar>!, _ len: Int) -> swiftparse_client_node_t!
                ^
    
    make: *** [build] Error 1
    
    opened by c128128 1
  • Cannot install Pecker on macOS Big Sur 11.3 (beta)

    Cannot install Pecker on macOS Big Sur 11.3 (beta)

    I cannot install Pecker on Big Sur (possibly due to being beta, but maybe you know).

    If trying via Homebrew it throws an error, and trying a manual install via make install throws the following build error:

    Cloning https://github.com/apple/swift-argument-parser.git
    Resolving https://github.com/apple/swift-argument-parser.git at main
    warning: Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from "More Downloads" for Apple Developers at https://developer.apple.com/download/more/
    warning: Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from "More Downloads" for Apple Developers at https://developer.apple.com/download/more/
    warning: Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from "More Downloads" for Apple Developers at https://developer.apple.com/download/more/
    /Users/christian/Pecker/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:32: warning: initialization of 'UnsafeMutablePointer<yaml_version_directive_t>' (aka 'UnsafeMutablePointer<yaml_version_directive_s>') results in a dangling pointer
                versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    /Users/christian/Pecker/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:53: note: implicit argument conversion from 'yaml_version_directive_t' (aka 'yaml_version_directive_s') to 'UnsafeMutablePointer<yaml_version_directive_t>' (aka 'UnsafeMutablePointer<yaml_version_directive_s>') produces a pointer valid only for the duration of the call to 'init(_:)'
                versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                                        ^~~~~~~~~~~~~~~~~~~~~~
    /Users/christian/Pecker/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:53: note: use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope
                versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                                        ^
    /Users/christian/Pecker/Sources/Pecker/PeckerCommand.swift:22:6: error: referencing initializer 'init(name:help:)' on 'Flag' requires the types 'Bool' and 'Int' be equivalent
        @Flag(name: .shortAndLong, help: "Print the version and exit")
         ^
    /Users/christian/Pecker/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsable Properties/Flag.swift:273:1: note: where 'Value' = 'Bool'
    extension Flag where Value == Int {
    ^
    
    make: *** [build] Error 1
    christian@Christians-MacBook-Pro Pecker % 
    
    opened by christiankm 5
  • False positive: `skip_public` does not apply to interfaces in public extension scope

    False positive: `skip_public` does not apply to interfaces in public extension scope

    With skip_public rule enabled (which already is by default),

    This case is detected as unused.

    public extension NSParagraphStyle {
        static var level1: NSParagraphStyle { ... }
    }
    

    This one is not.

    extension NSParagraphStyle {
        public static var level1: NSParagraphStyle { ... }
    }
    
    opened by tackgyu 1
Owner
Roy
Roy
Xcode storyboards diff and merge tool.

StoryboardMerge Storyboard diff and merge tool which: compares and merges two storyboard files, provides an automatic merge-facility, The storyboardin

null 238 Sep 12, 2022
An adorable little framework and command line tool for interacting with SourceKit.

SourceKitten An adorable little framework and command line tool for interacting with SourceKit. SourceKitten links and communicates with sourcekitd.fr

JP Simard 2.1k Jan 5, 2023
Xcode-compatible build tool.

xcbuild xcbuild is an Xcode-compatible build tool with the goal of providing faster builds, better documentation of the build process and running on m

Meta Archive 2k Dec 11, 2022
Laurine - Localization code generator written in Swift. Sweet!

Author's note: Thanks everyone for making Laurine the TOP trending Swift repository in the world - this is amazing and very heart-warming! But this is

Jiri Trecak 1.3k Dec 28, 2022
Objective-c code Apple style documentation set generator.

About appledoc IMPORTANT NOTICE: collaborators needed appledoc is command line tool that helps Objective-C developers generate Apple-like source code

tomaz 4.2k Dec 20, 2022
An Xcode plug-in to format your code using SwiftLint.

SwiftLintXcode An Xcode plug-in to format your code using SwiftLint. Runs swiftlint autocorrect --path CURRENT_FILE before *.swift file is saved. IMPO

Yuya Tanaka 348 Sep 18, 2022
Swift CLI for strong-typing images, colors, storyboards, fonts and localizations

Shark Shark is a Swift command line tool that generates type safe enums for your images, colors, storyboards, fonts and localizations. Because Shark r

Kaan Dedeoglu 377 Dec 1, 2022
Strong typed, autocompleted resources like images, fonts and segues in Swift projects

R.swift Get strong typed, autocompleted resources like images, fonts and segues in Swift projects Why use this? It makes your code that uses resources

Mathijs Kadijk 8.9k Jan 6, 2023
Soulful docs for Swift & Objective-C

jazzy is a command-line utility that generates documentation for Swift or Objective-C About Both Swift and Objective-C projects are supported. Instead

Realm 7.2k Jan 3, 2023
swiftenv allows you to easily install, and switch between multiple versions of Swift.

Swift Version Manager swiftenv allows you to easily install, and switch between multiple versions of Swift. This project was heavily inspired by pyenv

Kyle Fuller 1.9k Dec 27, 2022
Script to support easily using Xcode Asset Catalog in Swift.

Misen Misen is a script to support using Xcode Asset Catalog in Swift. Features Misen scans sub-directories in the specified Asset Catalog and creates

Kazunobu Tasaka 123 Jun 29, 2022
An Xcode Plugin to convert Objective-C to Swift

XCSwiftr Convert Objective-C code into Swift from within Xcode. This plugin uses the Java applet of objc2swift to do the conversion. Noticed that the

Ignacio Romero Zurbuchen 338 Nov 29, 2022
Swift autocompleter for Sublime Text, via the adorable SourceKitten framework

SwiftKitten SwiftKitten is a Swift autocompleter for Sublime Text, via the adorable SourceKitten framework. Faster than XCode ! This package is new an

John Snyder 142 Sep 9, 2022
Detect-a-Beacon - An educational application using Apple iBeacon technology to detect beacons

Detect-a-Beacon An educational application using Apple iBeacon technology to det

NIKOLAY NIKITIN 1 Aug 9, 2022
A tool for finding missing and unused NSLocalizedStrings

nslocalizer This is a command line tool that is used for discovering missing and unused localization strings in Xcode projects. Contributing and Code

Samantha Demi 155 Sep 17, 2022
Command line program that detects unused resource strings in an iOS or OS X application.

Abandoned Resource String Detection This command line program detects unused resource strings in an iOS or OS X application. Updated to Swift 3, thank

Josh Smith 360 Nov 26, 2022
🌊 A clean wave - report unused localized strings

Ripple ?? Ripple - a command line tool that reports unused localization strings in your Xcode project. Install Clone from repo git clone cd Ripple sw

Edoardo Benissimo 3 Sep 16, 2022
A phantom type is a custom type that has one or more unused type parameters.

PhantomTypes A phantom type is a custom type that has one or more unused type parameters. Phantom types allow you to enforce type-safety without sacri

null 3 Nov 4, 2022
The iOS pod which can collect profile data to detect code coverage.

CodeCoverageKit Installation CodeCoverageKit is available through CocoaPods.

木子 2 Sep 29, 2021
DevTool - A simple UI and powerful Mac OS application, Such as JSON-Formatting tool, JSON-to-model tool, AppIcon generator, Network-Request tool...

?? ?? ?? A simple UI and powerful Mac OS application. It is a collection of tools commonly used in my development work. Such as JSON-Formatting tool, JSON-to-model tool, AppIcon generator, Network-Request tool...

渠晓友 3 Dec 21, 2022