{"id":13568526,"url":"https://github.com/borkdude/carve","last_synced_at":"2025-04-04T10:05:28.993Z","repository":{"id":42699540,"uuid":"230975727","full_name":"borkdude/carve","owner":"borkdude","description":"Remove unused Clojure vars","archived":false,"fork":false,"pushed_at":"2024-02-01T13:21:16.000Z","size":493,"stargazers_count":290,"open_issues_count":3,"forks_count":17,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-30T03:43:11.162Z","etag":null,"topics":["babashka","clj-kondo","clojure"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/borkdude.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"github":"borkdude"}},"created_at":"2019-12-30T20:28:56.000Z","updated_at":"2024-10-27T02:42:10.000Z","dependencies_parsed_at":"2024-02-24T16:52:55.769Z","dependency_job_id":null,"html_url":"https://github.com/borkdude/carve","commit_stats":{"total_commits":158,"total_committers":15,"mean_commits":"10.533333333333333","dds":"0.18354430379746833","last_synced_commit":"e02b65f9de679450176a3fa26c89ffd5600d7eb8"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borkdude%2Fcarve","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borkdude%2Fcarve/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borkdude%2Fcarve/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borkdude%2Fcarve/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/borkdude","download_url":"https://codeload.github.com/borkdude/carve/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247157046,"owners_count":20893202,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["babashka","clj-kondo","clojure"],"created_at":"2024-08-01T14:00:27.452Z","updated_at":"2025-04-04T10:05:28.957Z","avatar_url":"https://github.com/borkdude.png","language":"Clojure","funding_links":["https://github.com/sponsors/borkdude"],"categories":["Clojure"],"sub_categories":[],"readme":"# Carve\n\n[![Clojars Project](https://img.shields.io/clojars/v/io.github.borkdude/carve.svg)](https://clojars.org/io.github.borkdude/carve)\n[![CircleCI](https://circleci.com/gh/borkdude/carve/tree/master.svg?style=shield)](https://circleci.com/gh/borkdude/carve/tree/master)\n[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://book.babashka.org#badges)\n\nCarve out the essentials of your Clojure app.\n\n## Rationale\n\nCarve will search through your code for unused vars and will remove them.\n\n## Installation\n\nAdd to your `deps.edn`:\n\n``` clojure\n:aliases {\n  ...\n  :carve {:extra-deps {io.github.borkdude/carve {:git/url \"https://github.com/borkdude/carve\"\n                                                 :git/sha \"\u003cSHA\u003e\"}}\n          :main-opts  [\"-m\" \"carve.main\"]}\n  ...\n}\n```\n\nor to your `bb.edn`:\n\n``` clojure\nio.github.borkdude/carve {:git/url \"https://github.com/borkdude/carve\"\n                          :git/sha \"\u003cSHA\u003e\"}\n```\n\nwhere the latest SHA can be found with:\n\n``` shell\n$ git ls-remote https://github.com/borkdude/carve.git refs/heads/master\n```\n\n### Babashka script\n\nYou can install carve as a babashka script that you can invoke as `carve` with:\n\n\n``` shell\n$ bbin install io.github.borkdude/carve\n```\n\nSee [bbin](https://github.com/babashka/bbin) for details.\n\n### Clojure tool\n\nTo use as a [clojure tool](https://clojure.org/reference/deps_and_cli#tool_install):\n\n``` shell\n$ clj -Ttools install io.github.borkdude/carve '{:git/tag \"v0.3.5\"}' :as carve\n```\n\n## How does it work?\n\nCarve invokes [clj-kondo](https://github.com/borkdude/clj-kondo) and uses the\n[analysis](https://github.com/borkdude/clj-kondo/tree/master/analysis)\ninformation to check which vars are unused. To remove the relevant bits of code\nit uses [rewrite-cljc](https://github.com/lread/rewrite-cljc-playground).\n\n## Usage\n\nThe usage for a typical Clojure app looks like:\n\n#### Babashka\n\nTo see help:\n\n``` shell\nbb -x carve.api/carve! --help\n```\n\nTo run with options:\n\n``` shell\nbb -x carve.api/carve! --paths src test\n```\n\n#### Clojure\n\nOn the JVM:\n\n```\nclojure -M:carve --paths src test\n```\n\nYou may provide options as an EDN literal with `--opts`, e.g.:\n\n``` shell\nclojure -M:carve --opts '{:paths [\"src\" \"test\"] :report {:format :text}}'\n```\n\nTo also load the config file in `.carve/config.edn` when passing `--opts`, set `:merge-config`:\n\n```shell\nclojure -M:carve --opts '{:interactive true :merge-config true}'\n```\n\n#### Clojure tool\n\nAs a [clojure tool](https://clojure.org/reference/deps_and_cli#_tool_usage):\n\n``` clojure\n$ clj -Tcarve carve! '{:paths [\"src\"] :report true :report-format :text}'\n```\n\n#### Options\n\nRun `carve --help` to see options.\n\nYou can also store the config for your project in `.carve/config.edn`. When\ninvoking carve with no options, the options in `.carve/config.edn` will be used.\nWhen providing options, the CLI options will take precedence over the configuration\nin `.carve/config.edn` and the file will be ignored. Pass `:merge-config` from the \nCLI to also load and merge `.carve/config.edn`.\n\nAll options:\n\n- `:paths`: a list of paths to analyze. Can be a mix of individual files and directories.\n- `:ignore-vars`: a list of vars to ignore. Useful for when the analyzer has it wrong or you just want to keep the var for whatever reason.\n- `:api-namespaces`: a list of namespaces of which only unused private vars will\n  be reported.\n- `:carve-ignore-file`: a file where ignored vars can be stored, `.carve/ignore`\n  by default.\n- `:interactive`: ask what to do with an unused var: remove from the file, add\n  to `.carve/ignore` or continue. Set to `true` by default.\n- `:merge-config`: Merge the CLI options and the config file together. Default is false.\n- `:out-dir`: instead of writing back to the original file, write to this dir.\n- `:dry-run`: just print the unused var expression.\n- `:aggressive`: runs multiple times until no unused vars are left. Defaults to `false`.\n- `:report`: when truthy, prints unused vars to stdout. Implies `:dry-run\n  true`. The output format may be set using `:report {:format ...}` where format\n  can be `:edn`, `:text` or `:ignore`. The text output can be interpreted by editors like\n  Emacs. This option can be combined with `:aggressive`.\n- `:silent`: when truthy, does not write to stdout. Implies `:interactive false`.\n- `:clj-kondo/config`: a map of clj-kondo config opts that are passed on to\n  clj-kondo, which is used to analyze usages. e.g.: passing `{:skip-comments\n  true}` will ignore function usage in `(comment)` forms. Note that the config\n  in `.clj-kondo/config.edn` is used as well - options passed with this key will\n  override options set in the clj-kondo config file.\n\n``` shell\n$ clojure -M:carve --paths \"test-resources\" --dry-run true\nCarving test-resources/app.clj\n\nFound unused var:\n(defn unused-function [])\n\n...\n\nCarving test-resources/api.clj\n\nFound unused var:\n(defn- private-lib-function [])\n\n...\n```\n\n``` shell\n$ clojure -M:carve --paths \"test-resources\"\nCarving test-resources/app.clj\n\nFound unused var:\n(defn unused-function [])\n\nType Y to remove or i to add app/unused-function to .carve/ignore\nn\nFound unused var:\n(defn another-unused-function [])\n\nType Y to remove or i to add app/another-unused-function to .carve/ignore\ni\n...\n\n$ cat .carve/ignore\napp/another-unused-function\n```\n\nKeep in mind that if you ran `carve` with `{:paths [\"src\" \"test\"]}`, there might still be potentially lots of unused code, which wasn't detected simply because there are tests for it.\n\nSo after a first cycle of carving you might want to do another run with simply `{:paths [\"src\"]}`, which will help deleting the rest of the unused code.\n*Just beware that this will break all the tests using the code you just deleted, and you'll have to fix/delete them manually.**\n\nCarve also removes any unused refers from namespace `:require` forms. This means\n_any_ unused refer, not just refers for functions determined to be unused by\ncarve.\n\n### CI integration\n\nA good use case for Carve is the CI integration, to ensure that no one can introduce dead code into a codebase.\nThis example shows how to add this step into CircleCI, but any other CI configuration will be similar.\n\nFirst add this configuration into a `.circleci/deps.edn` file:\n\n```clojure\n{:aliases\n {:carve {:extra-deps {io.github.borkdude/carve {:git/sha \"$LATEST_CARVE_SHA\"}}\n          :main-opts [\"-m\" \"carve.main\"]}}}\n```\n\nThen configure your build step like this:\n\n```yaml\nfind_dead_code:\n  working_directory: ~/$your-project\n  docker:\n    - image: circleci/clojure:openjdk-11-tools-deps\n\n  steps:\n    - checkout\n    - run: mkdir -p ~/.clojure \u0026\u0026 cp .circleci/deps.edn ~/.clojure/deps.edn\n    - run: clojure -M:carve --opts '{:paths [\"src\" \"test\"] :report {:format :text}}'\n```\n\nIf the `report` step finds any dead code it exits with status code `1`, thus failing the build step.\n\n### Emacs\n\n#### carve.el\n\nA simple emacs integration is provided by [carve.el](https://github.com/oliyh/carve.el).\n\nIt lets you run carve with a simple key binding and opens a result buffer where you can navigate to the results and add them to your ignore file with a single keystroke.\n\n#### Report mode\n\nRunning carve with in report mode (for example `clojure -M:carve --paths\nsrc test --report true --report-format :text}'`) you can make all the links clickable\nby switching to compilation-mode.\n\n\u003cimg src=\"assets/eshell.png\"\u003e\n\nUsing `:report-format :ignore` returns the list of unused vars in the same format as .carve/ignore so you can create the initial ignore file or append to an existing one.\nFor example with:\n\n    carve --paths \"src\" \"test\" --report true --report-format :ignore' \u003e .carve/ignore\n\n## Articles\n\n- [Carve that Clojure codebase](https://juxt.pro/blog/carve) by Andrea Crotti\n\n## Dev\n\n\n### Running tests\n\nIf you want to run tests in Emacs and Cider you need to use the test alias, or\nit will fail while trying to load the `test.check` library.  You can place this in\nyour `.dir-locals.el` file in the root directory to always use the test alias:\n\n```elisp\n((clojure-mode . ((cider-clojure-cli-global-options . \"-R:test\"))))\n```\n\nor alter the command used by `cider-jack-in` by prefixing the invocation with\n`C-u`.\n\n## License\n\nCopyright © 2019-2023 Michiel Borkent\n\nDistributed under the EPL License. See LICENSE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborkdude%2Fcarve","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fborkdude%2Fcarve","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborkdude%2Fcarve/lists"}