{"id":13526905,"url":"https://github.com/peripheryapp/periphery","last_synced_at":"2026-02-15T01:10:53.108Z","repository":{"id":37493421,"uuid":"169972846","full_name":"peripheryapp/periphery","owner":"peripheryapp","description":"A tool to identify unused code in Swift projects.","archived":false,"fork":false,"pushed_at":"2026-02-11T09:35:20.000Z","size":11692,"stargazers_count":6001,"open_issues_count":36,"forks_count":221,"subscribers_count":38,"default_branch":"master","last_synced_at":"2026-02-12T07:13:58.000Z","etag":null,"topics":["apple","ios","macos","swift","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/peripheryapp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"ileitch","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2019-02-10T11:53:24.000Z","updated_at":"2026-02-11T11:51:18.000Z","dependencies_parsed_at":"2023-09-26T12:21:53.977Z","dependency_job_id":"31bc660a-cfc0-4f77-bf1d-89db40af5b27","html_url":"https://github.com/peripheryapp/periphery","commit_stats":{"total_commits":467,"total_committers":26,"mean_commits":17.96153846153846,"dds":"0.48822269807280516","last_synced_commit":"c4adeac6525a3425d3460772270ccf249fc16e8e"},"previous_names":[],"tags_count":77,"template":false,"template_full_name":null,"purl":"pkg:github/peripheryapp/periphery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peripheryapp%2Fperiphery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peripheryapp%2Fperiphery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peripheryapp%2Fperiphery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peripheryapp%2Fperiphery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peripheryapp","download_url":"https://codeload.github.com/peripheryapp/periphery/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peripheryapp%2Fperiphery/sbom","scorecard":{"id":727912,"data":{"date":"2025-08-11","repo":{"name":"github.com/peripheryapp/periphery","commit":"7f0527946032b2908d8564ae4ca9256e019d7f51"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":10,"reason":"13 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Info: Possibly incomplete results: error parsing shell code: invalid parameter name: .github/workflows/test.yml:98","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:58: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:84: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:119: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:129: update your workflow using https://app.stepsecurity.io/secureworkflow/peripheryapp/periphery/test.yml/master?enable=pin","Warn: containerImage not pinned by hash: docker/Dockerfile.linux:1: pin your Docker image by updating swift:latest to swift:latest@sha256:5871217816a92778b7f01112fd43d8434e6216573c3f2442abc31ea99aee193e","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   3 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 3.2.0 not signed: https://api.github.com/repos/peripheryapp/periphery/releases/228376643","Warn: release artifact 3.1.0 not signed: https://api.github.com/repos/peripheryapp/periphery/releases/210564809","Warn: release artifact 3.0.3 not signed: https://api.github.com/repos/peripheryapp/periphery/releases/206016119","Warn: release artifact 3.0.2 not signed: https://api.github.com/repos/peripheryapp/periphery/releases/203339452","Warn: release artifact 3.0.1 not signed: https://api.github.com/repos/peripheryapp/periphery/releases/192588244","Warn: release artifact 3.2.0 does not have provenance: https://api.github.com/repos/peripheryapp/periphery/releases/228376643","Warn: release artifact 3.1.0 does not have provenance: https://api.github.com/repos/peripheryapp/periphery/releases/210564809","Warn: release artifact 3.0.3 does not have provenance: https://api.github.com/repos/peripheryapp/periphery/releases/206016119","Warn: release artifact 3.0.2 does not have provenance: https://api.github.com/repos/peripheryapp/periphery/releases/203339452","Warn: release artifact 3.0.1 does not have provenance: https://api.github.com/repos/peripheryapp/periphery/releases/192588244"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 14 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T13:25:52.654Z","repository_id":37493421,"created_at":"2025-08-22T13:25:52.654Z","updated_at":"2025-08-22T13:25:52.654Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29461618,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T22:42:09.113Z","status":"ssl_error","status_checked_at":"2026-02-14T22:42:05.053Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","ios","macos","swift","xcode"],"created_at":"2024-08-01T06:01:37.191Z","updated_at":"2026-02-15T01:10:53.100Z","avatar_url":"https://github.com/peripheryapp.png","language":"Swift","readme":"\u003ch1 align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.png\" alt=\"Periphery\" height=\"60\" /\u003e\n  \u003cbr\u003e\n  Periphery\n\u003c/h1\u003e\n\n\u003ch4 align=\"center\"\u003eA tool to identify unused code in Swift projects.\u003c/h4\u003e\n\u003cp align=\"center\"\u003e\u003cq\u003e\u003ci\u003eNow I am become Delete, the destroyer of codes.\u003c/i\u003e\u003c/q\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/peripheryapp/periphery/releases/latest\"\u003e\n\u003cimg src=\"https://img.shields.io/github/release/peripheryapp/periphery.svg?color=008DFF\"/\u003e\u003c/a\u003e\n\u003cimg src=\"https://img.shields.io/badge/platform-macOS%20|%20Linux-008DFF\"\u003e\n\u003ca href=\"#sponsors-\"\u003e\n\u003cimg src=\"https://img.shields.io/github/sponsors/peripheryapp?logo=githubsponsors\u0026color=db61a2\"\u003e\n\u003c/a\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003c/p\u003e\n\n## Contents\n\n- [Installation](#installation)\n- [How To Use](#how-to-use)\n- [Analysis](#analysis)\n    - [Function Parameters](#function-parameters)\n    - [Protocols](#protocols-1)\n    - [Enumerations](#enumerations)\n    - [Assign-only Properties](#assign-only-properties)\n    - [Redundant Public Accessibility](#redundant-public-accessibility)\n    - [Unused Imports](#unused-imports)\n    - [Objective-C](#objective-c)\n    - [Codable](#codable)\n    - [XCTestCase](#xctestcase)\n    - [Interface Builder](#interface-builder)\n    - [SPI (System Programming Interface)](#spi-system-programming-interface)\n- [Comment Commands](#comment-commands)\n- [Xcode Integration](#xcode-integration)\n- [Excluding Files](#excluding-files)\n- [Continuous Integration](#continuous-integration)\n- [Build Systems](#build-systems)\n- [Platforms](#platforms)\n- [Troubleshooting](#troubleshooting)\n- [Known Bugs](#known-bugs)\n- [Sponsors](#sponsors-) ![Sponsors](assets/sponsor.svg)\n\n## Installation\n\n### [Homebrew](https://brew.sh/)\n\n```sh\nbrew install periphery\n```\n\n### [Mint](https://github.com/yonaskolb/mint)\n\n```sh\nmint install peripheryapp/periphery\n```\n\n### [Bazel](https://bazel.build/)\n\n```python\nbazel_dep(name = \"periphery\", version = \"\u003cversion\u003e\", dev_dependency = True)\nuse_repo(use_extension(\"@periphery//bazel:generated.bzl\", \"generated\"), \"periphery_generated\")\n```\n\nSee [Bazel](#build-systems) below for usage instructions.\n\n## How To Use\n\n### The `scan` Command\n\nThe scan command is Periphery's primary function. To begin a guided setup, change to your project directory and run:\n\n```sh\nperiphery scan --setup\n```\n\nThe guided setup will detect your project type and configure a few options. After answering a few questions, Periphery will print out the full scan command and execute it.\n\nThe guided setup is only intended for introductory purposes. Once you are familiar with Periphery, you can try some more advanced options, all of which can be seen with `periphery help scan`.\n\nTo get the most from Periphery, it’s important to understand how it works. Periphery first builds your project; it does this to generate the “index store”. The index store contains detailed information about the declarations (class, struct, func, etc.) in your project and their references to other declarations. Using this store, Periphery builds an in-memory graph of the relational structure of your project, supplementing it with additional information obtained by parsing each source file. Next, the graph is mutated to make it more suitable for detecting unused code, e.g., marking your project’s entry points. Finally, the graph is traversed from its roots to identify unreferenced declarations.\n\n\u003e [!TIP]\n\u003e The index store only contains information about source files in the build targets compiled during the build phase. If a given class is only referenced in a source file that was not compiled, then Periphery will identify the class as unused. It's important to ensure you build all the targets you expect to contain references. For an Xcode project, this is controlled using the `--schemes` option. For a Swift package, all targets are built automatically.\n\nIf your project consists of one or more standalone frameworks that do not also contain some kind of application that consumes their interfaces, you need to tell Periphery to assume that all public declarations are used with the `--retain-public` option.\n\nFor projects that are mixed Objective-C \u0026 Swift, it's highly recommended you [read about the implications](#objective-c) this can have on your results.\n\n### Configuration\n\nOnce you've settled upon the appropriate options for your project, you may wish to persist them in a YAML configuration file. The simplest way to achieve this is to run Periphery with the `--verbose` option. Near the beginning of the output, you will see the `[configuration:begin]` section with your configuration formatted as YAML below. Copy \u0026 paste the configuration into `.periphery.yml` in the root of your project folder. You can now simply run `periphery scan` and the YAML configuration will be used.\n\n## Analysis\n\nThe goal of Periphery is to report instances of unused _declarations_. A declaration is a `class`, `struct`, `protocol`, `function`, `property`, `constructor`, `enum`, `typealias`, `associatedtype`, etc. As you'd expect, Periphery can identify simple unreferenced declarations, e.g., a `class` that is no longer used anywhere in your codebase.\n\nPeriphery can also identify more advanced instances of unused code. The following section explains these in detail.\n\n### Function Parameters\n\nPeriphery can identify unused function parameters. Instances of unused parameters can also be identified in protocols and their conforming declarations, as well as parameters in overridden methods. Both of these scenarios are explained further below.\n\n#### Protocols\n\nAn unused parameter of a protocol function will only be reported as unused if the parameter is also unused in all implementations.\n\n```swift\nprotocol Greeter {\n    func greet(name: String)\n    func farewell(name: String) // 'name' is unused\n}\n\nclass InformalGreeter: Greeter {\n    func greet(name: String) {\n        print(\"Sup \" + name + \".\")\n    }\n\n    func farewell(name: String) { // 'name' is unused\n      print(\"Cya.\")\n    }\n}\n```\n\n\u003e [!TIP]\n\u003e You can ignore all unused parameters from protocols and conforming functions with the `--retain-unused-protocol-func-params` option.\n\n#### Overrides\n\nSimilar to protocols, parameters of overridden functions are only reported as unused if they're also unused in the base function and all overriding functions.\n\n```swift\nclass BaseGreeter {\n    func greet(name: String) {\n        print(\"Hello.\")\n    }\n\n    func farewell(name: String) { // 'name' is unused\n        print(\"Goodbye.\")\n    }\n}\n\nclass InformalGreeter: BaseGreeter {\n    override func greet(name: String) {\n        print(\"Sup \" + name + \".\")\n    }\n\n    override func farewell(name: String) { // 'name' is unused\n        print(\"Cya.\")\n    }\n}\n```\n\n#### Foreign Protocols \u0026 Classes\n\nUnused parameters of protocols or classes defined in foreign modules (e.g. Foundation) are always ignored since you do not have access to modify the base function declaration.\n\n#### fatalError Functions\n\nUnused parameters of functions that simply call `fatalError` are also ignored. Such functions are often unimplemented required initializers in subclasses.\n\n```swift\nclass Base {\n    let param: String\n\n    required init(param: String) {\n        self.param = param\n    }\n}\n\nclass Subclass: Base {\n    init(custom: String) {\n        super.init(param: custom)\n    }\n\n    required init(param: String) {\n        fatalError(\"init(param:) has not been implemented\")\n    }\n}\n```\n\n### Protocols\n\nA protocol that is conformed to by an object is not truly used unless it's also used as an existential type or to specialize a generic method/class. Periphery is able to identify such redundant protocols whether they are conformed to by one or even multiple objects.\n\n```swift\nprotocol MyProtocol { // 'MyProtocol' is redundant\n    func someMethod()\n}\n\nclass MyClass1: MyProtocol { // 'MyProtocol' conformance is redundant\n    func someMethod() {\n        print(\"Hello from MyClass1!\")\n    }\n}\n\nclass MyClass2: MyProtocol { // 'MyProtocol' conformance is redundant\n    func someMethod() {\n        print(\"Hello from MyClass2!\")\n    }\n}\n\nlet myClass1 = MyClass1()\nmyClass1.someMethod()\n\nlet myClass2 = MyClass2()\nmyClass2.someMethod()\n```\n\nHere we can see that despite both implementations of `someMethod` being called, at no point does an object take on the type of `MyProtocol`. Therefore, the protocol itself is redundant, and there's no benefit from `MyClass1` or `MyClass2` conforming to it. We can remove `MyProtocol` along with each redundant conformance and just keep `someMethod` in each class.\n\nJust like a normal method or property of an object, individual properties and methods declared by your protocol can also be identified as unused.\n\n```swift\nprotocol MyProtocol {\n    var usedProperty: String { get }\n    var unusedProperty: String { get } // 'unusedProperty' is unused\n}\n\nclass MyConformingClass: MyProtocol {\n    var usedProperty: String = \"used\"\n    var unusedProperty: String = \"unused\" // 'unusedProperty' is unused\n}\n\nclass MyClass {\n    let conformingClass: MyProtocol\n\n    init() {\n        conformingClass = MyConformingClass()\n    }\n\n    func perform() {\n        print(conformingClass.usedProperty)\n    }\n}\n\nlet myClass = MyClass()\nmyClass.perform()\n```\n\nHere we can see that `MyProtocol` is itself used and cannot be removed. However, since `unusedProperty` is never called on `MyConformingClass`, Periphery can identify that the declaration of `unusedProperty` in `MyProtocol` is also unused and can be removed along with the unused implementation of `unusedProperty`.\n\n### Enumerations\n\nAlong with being able to identify unused enumerations, Periphery can also identify individual unused enum cases. Plain enums that are not raw representable, i.e., that _don't_ have a `String`, `Character`, `Int`, or floating-point value type, can be reliably identified. However, enumerations that _do_ have a raw value type can be dynamic, and therefore must be assumed to be used.\n\nLet's clear this up with a quick example:\n\n```swift\nenum MyEnum: String {\n    case myCase\n}\n\nfunc someFunction(value: String) {\n    if let myEnum = MyEnum(rawValue: value) {\n        somethingImportant(myEnum)\n    }\n}\n```\n\nThere's no direct reference to the `myCase` case, so it's reasonable to expect it _might_ no longer be needed. However, if it were removed, we can see that `somethingImportant` would never be called if `someFunction` were passed the value of `\"myCase\"`.\n\n### Assign-only Properties\n\nProperties that are assigned but never used are identified as such, e.g.:\n\n```swift\nclass MyClass {\n    var assignOnlyProperty: String // 'assignOnlyProperty' is assigned, but never used\n\n    init(value: String) {\n        self.assignOnlyProperty = value\n    }\n}\n```\n\nIn some cases this may be the intended behavior; therefore, you have a few options available to silence such results:\n\n- Retain individual properties using [Comment Commands](#comment-commands).\n- Retain all assign-only properties by their type with `--retain-assign-only-property-types`. Given types must match their exact usage in the property declaration (sans optional question mark), e.g. `String`, `[String]`, `Set\u003cString\u003e`. Periphery is unable to resolve inferred property types, therefore in some instances, you may need to add explicit type annotations to your properties.\n- Disable assign-only property analysis entirely with `--retain-assign-only-properties`.\n\n### Redundant Public Accessibility\n\nDeclarations that are marked `public` yet are not referenced from outside their home module are identified as having redundant public accessibility. In this scenario, the `public` annotation can be removed from the declaration. Removing redundant public accessibility has a couple of benefits:\n\n- It helps reduce the public surface area of your modules.\n- In [Whole Module Compilation](https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#whole-module-optimizations-wmo) mode, Swift can infer `final` by [automatically discovering](https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#advice-if-wmo-is-enabled-use-internal-when-a-declaration-does-not-need-to-be-accessed-outside-of-module) all potentially overriding declarations. `final` classes are [better optimized](https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#advice-use-final-when-you-know-the-declaration-does-not-need-to-be-overridden) by the compiler.\n\nThis analysis can be disabled with `--disable-redundant-public-analysis`.\n\n### Unused Imports\n\nPeriphery can only detect unused imports of targets it has scanned. It cannot detect unused imports of other targets because the Swift source files are unavailable and uses of `@_exported` cannot be observed. `@_exported` is problematic because it changes the public interface of a target such that the declarations exported by the target are no longer necessarily declared by the imported target. For example, the `Foundation` target exports `Dispatch`, among other targets. If any given source file imports `Foundation` and references `DispatchQueue` but no other declarations from `Foundation`, then the `Foundation` import cannot be removed as it would also make the `DispatchQueue` type unavailable. To avoid false positives, therefore, Periphery only detects unused imports of targets it has scanned.\n\nPeriphery will likely produce false positives for targets with mixed Swift and Objective-C, as Periphery cannot scan the Objective-C files. It is recommended, therefore, to disable unused import detection for projects with a significant amount of Objective-C or manually exclude the mixed language targets from the results.\n\n### Objective-C\n\nPeriphery cannot analyze Objective-C code since types may be dynamically typed.\n\nBy default, Periphery does not assume that declarations accessible by the Objective-C runtime are in use. If your project is a mix of Swift \u0026 Objective-C, you can enable this behavior with the `--retain-objc-accessible` option. Swift declarations that are accessible by the Objective-C runtime are those that are explicitly annotated with `@objc` or `@objcMembers`, and classes that inherit `NSObject` either directly or indirectly via another class.\n\nAlternatively, the `--retain-objc-annotated` option can be used to only retain declarations that are explicitly annotated with `@objc` or `@objcMembers`. Types that inherit `NSObject` are not retained unless they have explicit annotations. This option may uncover more unused code, but with the caveat that some of the results may be incorrect if the declaration is used in Objective-C code. To resolve these incorrect results, you must add an `@objc` annotation to the declaration.\n\n### Codable\n\nSwift synthesizes additional code for `Codable` types that is not visible to Periphery and can result in false positives for properties not directly referenced from non-synthesized code. If your project contains many such types, you can retain all properties on `Codable` types with `--retain-codable-properties`. Alternatively, you can retain properties only on `Encodable` types with `--retain-encodable-properties`.\n\nIf `Codable` conformance is declared by a protocol in an external module not scanned by Periphery, you can instruct Periphery to identify the protocols as `Codable` with `--external-codable-protocols \"ExternalProtocol\"`.\n\n### XCTestCase\n\nAny class that inherits `XCTestCase` is automatically retained along with its test methods. However, when a class inherits `XCTestCase` indirectly via another class, e.g., `UnitTestCase`, and that class resides in a target that isn't scanned by Periphery, you need to use the `--external-test-case-classes UnitTestCase` option to instruct Periphery to treat `UnitTestCase` as an `XCTestCase` subclass.\n\n### Interface Builder\n\nIf your project contains Interface Builder files (such as storyboards and XIBs), Periphery will take these into account when identifying unused declarations. Periphery parses these files to identify which classes, `@IBOutlet` properties, `@IBAction` methods, and `@IBInspectable` properties are actually referenced. Only those members that are connected in the Interface Builder file will be retained. Any `@IB*` members that are declared but not connected will be reported as unused.\n\n### SPI (System Programming Interface)\n\nSwift's `@_spi` attribute marks declarations as pseudo-private, making them accessible only to clients that explicitly import the SPI. While these declarations are technically `public`, they're intended for internal or restricted use.\n\nWhen using `--retain-public` for framework projects, all public declarations are retained, including those marked with `@_spi`. However, you may want to check for unused code within specific SPIs. The `--no-retain-spi` option allows you to specify which SPIs should be checked for unused code even when `--retain-public` is enabled.\n\nFor example, with `--retain-public --no-retain-spi Internal`, Periphery will:\n- Retain regular `public` declarations\n- Retain `@_spi(Testing) public` declarations (different SPI)\n- **Check** `@_spi(Internal) public` declarations for unused code\n\nThis is particularly useful for internal SPIs that should be audited for unused code while still retaining the framework's public API.\n\n## Comment Commands\n\nFor whatever reason, you may want to keep some unused code. Source code comment commands can be used to ignore specific declarations and exclude them from the results.\n\nAn ignore comment command can be placed directly on the line above any declaration to ignore it and all descendent declarations:\n\n```swift\n// periphery:ignore\nclass MyClass {}\n```\n\nYou can also ignore specific unused function parameters:\n\n```swift\n// periphery:ignore:parameters unusedOne,unusedTwo\nfunc someFunc(used: String, unusedOne: String, unusedTwo: String) {\n    print(used)\n}\n```\n\nThe `// periphery:ignore:all` command can be placed at the top of the source file to ignore the entire contents of the file. Note that the comment must be placed above any code, including import statements.\n\nComment commands also support trailing comments following a hyphen so that you can include an explanation on the same line:\n\n```swift\n// periphery:ignore - explanation of why this is necessary\nclass MyClass {}\n```\n\n### Overriding Result Kind and Location\n\nIn generated code scenarios where the generated code is too low-level or obtuse to be directly reported as unused, you can override the `kind` and/or `location` of a result to provide a more meaningful report:\n\n```swift\n// periphery:override kind=\"MyCustomThing\" location=\"path/to/file.swift:42:1\"\nfunc generatedFunction() {}\n```\n\nThe `kind` override allows you to specify a custom kind that will be shown in the result. The `location` override uses the format `file:line:column` (line and column default to 1 if omitted). This is particularly useful when you want to report results at a higher-level definition rather than at the low-level generated code. The `location` file path is assumed to be relative to the project root if a relative path is given.\n\n## Xcode Integration\n\nBefore setting up Xcode integration, first get Periphery working in a terminal, as you will be using the same command via Xcode. Your project may require passing the `-destination` argument to xcodebuild. This can be done by supplying it as an additional argument, e.g. `periphery scan ... -- -destination \"generic/platform=iOS Simulator\"`.\n\n### Step 1: Create an Aggregate Target\n\nSelect your project in the Project Navigator and click the + button at the bottom left of the Targets section. Select **Other** and choose **Aggregate**. Hit Next.\n\n![Step 1](assets/xcode-integration/1.png)\n\nChoose a name for the new target, e.g., \"Periphery\" or \"Unused Code\".\n\n![Step 2](assets/xcode-integration/2.png)\n\n### Step 2: Add a Run Script Build Phase\n\nIn the **Build Phases** section, click the + button to add a new Run Script phase.\n\n![Step 3](assets/xcode-integration/3.png)\n\nCopy and paste your Periphery command into the script input.\n\n\u003e [!TIP]\n\u003e 1. Include the `--format xcode` option to ensure results are always formatted so that Xcode can parse them.\n\u003e 2. Use the absolute path to `periphery`.\n\n![Step 4](assets/xcode-integration/4.png)\n\n### Step 3: Disable User Script Sandboxing\n\nYou must disable **User Script Sandboxing** for the Run Script phase. Periphery requires access to your project's index store and source files, which are blocked by Xcode's default sandbox. To disable sandboxing, set the `ENABLE_USER_SCRIPT_SANDBOXING` option to `No` in the Build Settings for the Periphery aggregate target.\n\n![Step 4](assets/xcode-integration/5.png)\n\n### Step 4: Select \u0026 Run\n\nYou're ready to roll. You should now see the new scheme in the dropdown. Select it and hit run.\n\n\u003e [!TIP]\n\u003e If you'd like others on your team to be able to use the scheme, you'll need to mark it as _Shared_. This can be done by selecting _Manage Schemes..._ and selecting the _Shared_ checkbox next to the new scheme. The scheme definition can now be checked into source control.\n\n![Step 5](assets/xcode-integration/6.png)\n\n## Excluding Files\n\nBoth exclusion options described below accept a Bash v4-style path glob, either absolute or relative to your project directory. You can delimit multiple globs with a space, e.g., `--option \"Sources/Single.swift\" \"**/Generated/*.swift\" \"**/*.{xib,storyboard}\"`.\n\n### Excluding Results\n\nTo exclude the results from certain files, pass the `--report-exclude \u003cglobs\u003e` option to the `scan` command.\n\n### Excluding Indexed Files\n\nExcluding files from the indexing phase means that any declarations and references contained within the files will not be seen by Periphery. Periphery will behave as if the files do not exist.\n\nTo exclude files from being indexed, there are a few options:\n\n1. Use `--exclude-targets \"TargetA\" \"TargetB\"` to exclude all source files in the chosen targets.\n2. Use `--exclude-tests` to exclude all test targets.\n3. Use `--index-exclude \"file.swift\" \"path/*.swift\"` to exclude individual source files.\n\n### Retaining File Declarations\n\nTo retain all declarations in files, pass the `--retain-files \u003cglobs\u003e` option to the `scan` command. This option is equivalent to adding a `// periphery:ignore:all` comment command at the top of each file.\n\n## Continuous Integration\n\nWhen integrating Periphery into a CI pipeline, you can potentially skip the build phase if your pipeline has already done so, e.g., to run tests. This can be achieved using the `--skip-build` option. However, you also need to tell Periphery the location of the index store using `--index-store-path`. This location is dependent on your project type.\n\n### Xcode\n\nThe index store generated by `xcodebuild` exists in DerivedData at a location dependent on your project, e.g., `~/Library/Developer/Xcode/DerivedData/YourProject-abc123/Index/DataStore`. For Xcode 14 and later, the `Index` directory can be found as `Index.noindex`, which suppresses Spotlight indexing.\n\n### SwiftPM\n\nBy default, Periphery looks for the index store at `.build/debug/index/store`. Therefore, if you intend to run Periphery directly after calling `swift test`, you can omit the `--index-store-path` option, and Periphery will use the index store created when the project was built for testing. However, if this isn't the case, then you must provide Periphery the location of the index store with `--index-store-path`.\n\n## Build Systems\n\n### Bazel\n\n```sh\nbazel run @periphery -- scan --bazel\n```\n\nThe `--bazel` option enables Bazel mode, which provides seamless integration with your project. It works by querying your project to identify all top-level targets, generating a hidden implementation of the [scan](https://github.com/peripheryapp/periphery/blob/master/bazel/rules.bzl) rule, and then invoking `bazel run`. You can filter the top-level targets with the `--bazel-filter \u003cvalue\u003e` option, where `\u003cvalue\u003e` will be passed as the first argument to Bazel's [filter](https://bazel.build/query/language#filter) operator. The generated query can be seen in the console with the `--verbose` option.\n\n### Other\n\nPeriphery can analyze projects using other build systems, though it cannot drive them automatically like SPM, Xcode, and Bazel. Instead, you need to create a configuration file that specifies the location of indexstore and other resource files. The format is as follows:\n\n```json\n{\n    \"indexstores\": [\n        \"path/to/file.indexstore\"\n    ],\n    \"test_targets\": [\n        \"MyTests\"\n    ],\n    \"plists\": [\n        \"path/to/file.plist\"\n    ],\n    \"xibs\": [\n        \"path/to/file.xib\",\n        \"path/to/file.storyboard\"\n    ],\n    \"xcdatamodels\": [\n        \"path/to/file.xcdatamodel\"\n    ],\n    \"xcmappingmodels\": [\n        \"path/to/file.xcmappingmodel\"\n    ]\n}\n```\n\n\u003e [!TIP]\n\u003e Relative paths are assumed to be relative to the current directory.\n\nYou can then invoke Periphery as follows:\n\n```sh\nperiphery scan --generic-project-config config.json\n```\n\n\u003e [!TIP]\n\u003e Both options support multiple paths.\n\n## Platforms\n\nPeriphery supports both macOS and Linux. macOS supports both Xcode and Swift Package Manager (SPM) projects, whereas only SPM projects are supported on Linux.\n\n## Troubleshooting\n\n### Erroneous results in one or more files, such as false positives and incorrect source file locations\n\nIt's possible for the index store to become corrupt or out of sync with the source file. This can happen, for example, if you forcefully terminate (^C) a scan. To rectify this, you can pass the `--clean-build` flag to the scan command to force removal of existing build artifacts.\n\n### Code referenced within a preprocessor macro conditional branch is unused\n\nWhen Periphery builds your project, it uses the default build configuration, which is typically 'debug'. If you use preprocessor macros to conditionally compile code, Periphery will only have visibility into the branches that are compiled. In the example below, `releaseName` will be reported as unused as it is only referenced within the non-debug branch of the macro.\n\n```swift\nstruct BuildInfo {\n    let debugName = \"debug\"\n    let releaseName = \"release\" // 'releaseName' is unused\n\n    var name: String {\n        #if DEBUG\n        debugName\n        #else\n        releaseName\n        #endif\n    }\n}\n```\n\nYou have a few options to work around this:\n\n- Use [Comment Commands](#comment-commands) to explicitly ignore `releaseName`.\n- Filter the results to remove known instances.\n- Run Periphery once for each build configuration and merge the results. You can pass arguments to the underlying build by specifying them after `--`, e.g., `periphery scan ... -- -configuration release`.\n\n### Swift package is platform-specific\n\nPeriphery uses `swift build` to compile a Swift package, which will fail if the Swift package is platform-specific (e.g., to iOS).\n\nAs a workaround, you can manually build the Swift package with `xcodebuild` and then use the `--skip-build` and `--index-store-path` options to target the index store previously produced by `xcodebuild`.\n\nExample:\n\n```sh\n# 1. Use xcodebuild\nxcodebuild -scheme MyScheme -destination 'platform=iOS Simulator,OS=16.2,name=iPhone 14' -derivedDataPath '../dd' clean build\n\n# 2. Use produced index store for scanning\nperiphery scan --skip-build --index-store-path '../dd/Index.noindex/DataStore/'\n```\n\n## Known Bugs\n\nDue to some underlying bugs in Swift, Periphery may in some instances report incorrect results.\n\n| ID    | Title |\n| :---  | :---  |\n| [56541](https://github.com/apple/swift/issues/56541) | Index store does not relate static property getter used as subscript key |\n| [56327](https://github.com/apple/swift/issues/56327) | Index store does not relate objc optional protocol method implemented in subclass |\n| [56189](https://github.com/apple/swift/issues/56189) | Index store should relate appendInterpolation from string literals |\n| [56165](https://github.com/apple/swift/issues/56165) | Index store does not relate constructor via literal notation |\n\n## Sponsors ![Sponsors](assets/sponsor-20.svg)\n\nPeriphery is a passion project that takes a huge amount of effort to maintain and develop. If you find Periphery useful, please consider sponsoring through [GitHub Sponsors](https://github.com/sponsors/peripheryapp).\n\nSpecial thanks go to the following generous sponsors:\n\n### SaGa Corp\n\n[SaGa Corp](https://www.sagacorp.fr) develops unique technology for financial players and their customers.\n\n\u003ca href=\"https://www.sagacorp.fr\" alt=\"SaGa Corp\"\u003e\n    \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/peripheryapp/periphery/raw/master/assets/sponsors/saga-corp-white.svg\"\u003e\n        \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/peripheryapp/periphery/raw/master/assets/sponsors/saga-corp-black.svg\"\u003e\n        \u003cimg src=\"https://github.com/peripheryapp/periphery/raw/master/assets/sponsors/saga-corp-black.svg\" width=\"150\"\u003e\n    \u003c/picture\u003e\n\u003c/a\u003e\n\n### Emerge Tools\n\n[Emerge Tools](https://www.emergetools.com) is a suite of revolutionary products designed to supercharge mobile apps and the teams that build them.\n\n\u003ca href=\"https://www.emergetools.com\" alt=\"Emerge Tools\"\u003e\n    \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/peripheryapp/periphery/raw/master/assets/sponsors/emerge-tools-vertical-white.svg\"\u003e\n        \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/peripheryapp/periphery/raw/master/assets/sponsors/emerge-tools-vertical-black.svg\"\u003e\n        \u003cimg src=\"https://github.com/peripheryapp/periphery/raw/master/assets/sponsors/emerge-tools-vertical-black.svg\"\u003e\n    \u003c/picture\u003e\n\u003c/a\u003e\n","funding_links":["https://github.com/sponsors/ileitch","https://github.com/sponsors/peripheryapp"],"categories":["Libs","Swift"],"sub_categories":["Utility"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperipheryapp%2Fperiphery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fperipheryapp%2Fperiphery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperipheryapp%2Fperiphery/lists"}