{"id":18081982,"url":"https://github.com/vindarel/colisper","last_synced_at":"2025-04-05T22:29:19.782Z","repository":{"id":44992166,"uuid":"295700862","full_name":"vindarel/colisper","owner":"vindarel","description":"Check and transform Lisp code with Comby (beta)","archived":false,"fork":false,"pushed_at":"2025-01-18T12:46:08.000Z","size":130,"stargazers_count":26,"open_issues_count":1,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-12T04:17:36.140Z","etag":null,"topics":["comby","common-lisp","elisp","lisp","refactoring"],"latest_commit_sha":null,"homepage":"","language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vindarel.png","metadata":{"files":{"readme":"README.markdown","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["vindarel"],"ko_fi":"vindarel"}},"created_at":"2020-09-15T11:07:24.000Z","updated_at":"2025-01-18T12:46:09.000Z","dependencies_parsed_at":"2022-08-04T01:15:28.482Z","dependency_job_id":null,"html_url":"https://github.com/vindarel/colisper","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vindarel%2Fcolisper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vindarel%2Fcolisper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vindarel%2Fcolisper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vindarel%2Fcolisper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vindarel","download_url":"https://codeload.github.com/vindarel/colisper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247411241,"owners_count":20934650,"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":["comby","common-lisp","elisp","lisp","refactoring"],"created_at":"2024-10-31T13:17:41.667Z","updated_at":"2025-04-05T22:29:19.755Z","avatar_url":"https://github.com/vindarel.png","language":"Emacs Lisp","funding_links":["https://github.com/sponsors/vindarel","https://ko-fi.com/vindarel","https://ko-fi.com/K3K828W0V'"],"categories":[],"sub_categories":[],"readme":"# colisper: static code checking and refactoring with [Comby](https://comby.dev/).\n\n*defined for Common Lisp, could work for any Lisp*\n\nStatus: beta, usable.\n\nComby makes it easy to match code structures. It can output a diff or\nchange the code in-place.\n\nWe define rules for lisp.\n\nWe can call them from our favorite editor (Emacs) during development.\n\nAnd we can run them as a pre-commit hook or in a CI.\n\n![](colisper-cli.png)\n\n\u003c!-- drop the first elt:\n (custom-set-variables '(markdown-toc-user-toc-structure-manipulation-fn 'cdr))\n --\u003e\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n**Table of Contents**\n\n- [Demo](#demo)\n    - [Transform `format t …` to `log:debug`](#transform-format-t--to-logdebug)\n    - [Remove any `print`](#remove-any-print)\n    - [Rewrite `if … progn` to `when`](#rewrite-if--progn-to-when)\n    - [Other rules](#other-rules)\n- [Installation](#installation)\n- [Run all rules with a script](#run-all-rules-with-a-script)\n    - [Run on a project](#run-on-a-project)\n- [Emacs integration](#emacs-integration)\n    - [Customization](#customization)\n- [CHANGELOG](#changelog)\n- [TODOs and ideas](#todos-and-ideas)\n- [See also:](#see-also)\n- [Final words](#final-words)\n\n\u003c!-- markdown-toc end --\u003e\n\n\n## Demo\n\nHere are my practical use cases.\n\nYou can try by cloning the repo and using this comby command:\n\n    colisper tests/playground.lisp\n\naka\n\n    comby -config ~/path/to/combycl/src/catalog/lisp/base -f tests/playground.lisp\n\na one-liner with inline rewrite rules looks like:\n\n    comby '(print :[rest])' ':[rest]' tests/playground.lisp\n\n### Transform `format t …` to `log:debug`\n\nWe are writing Lisp when suddenly, we want to rewrite some `format` to `log:debug` (or the contrary).\n\n```lisp\n(defun product-create-route/post (\"/create\" :method :post)\n  (title price)\n  (format t \"title is ~a~\u0026\" title)\n  (format t \"price is ~a~\u0026\" price)\n  (handler-case\n      (make-product :title title)\n    (error (c)\n      (format *error-output* \"ooops: ~a\" c)))\n  (render-template* +product-created.html+ nil))\n```\n\nI call `M-x colisper--format-to-debug` (or I use a Hydra to find the rule among others) and I get:\n\n\n```dif\n@@ -226,12 +226,12 @@ Dev helpers:\n\n (defun product-create-route/post (\"/create\" :method :post)\n   (title price)\n-  (format t \"title is ~a~\u0026\" title)\n-  (format t \"price is ~a~\u0026\" price)\n+  (log:debug \"title is ~a~\u0026\" title)\n+  (log:debug \"price is ~a~\u0026\" price)\n   (handler-case\n       (make-product :title title)\n     (error (c)\n-      (format *error-output* \"ooops: ~a\" c)))\n+      (log:debug \"ooops: ~a\" c)))\n   (render-template* +product-created.html+ nil))\n```\n\nWith Comby:\n\n    comby 'format :[stream] :[rest]' 'log:debug :[rest]' file.lisp\n\nIt seems that the search \u0026 replace is simple enough and that we don't\nleverage Comby's power here. But Comby works easily with multilines,\ncomments, and it will shine even more when we match s-expressions delimiters.\n\n### Remove any `print`\n\nWe are using `print` for debugging purposes when suddenly, our code is\nready for production use.\n\n    M-x colisper--remove-print\n\n```lisp\n(push (hunchentoot:create-folder-dispatcher-and-handler\n         \"/static/\" (print (merge-pathnames *default-static-directory*\n                                            (asdf:system-source-directory :abstock))))\n        hunchentoot:*dispatch-table*)\n```\n\n~~~lisp\n(push (hunchentoot:create-folder-dispatcher-and-handler\n         \"/static/\" (merge-pathnames *default-static-directory*\n                                     (asdf:system-source-directory :abstock)))\n        hunchentoot:*dispatch-table*)\n~~~\n\n\n### Rewrite `if … progn` to `when`\n\nRewrite:\n\n```lisp\n(if (and (getf options :version)\n             (foo)\n             ;; comment (with parens even\n             #| nasty comment:\n             (if (test) (progn even)))\n             |#\n         (bar))\n    (progn\n      (format t \"Project version ~a~\u0026\" +version+)\n      (print-system-info)\n      (uiop:quit)))\n```\n\nto:\n\n```lisp\n(when (and (getf options :version)\n           (foo)\n           ;; comment (with parens even\n           #| nasty comment:\n           (if (test) (progn even)))\n           |#\n           (bar))\n  (format t \"Project version ~a~\u0026\" +version+)\n  (print-system-info)\n  (uiop:quit))\n```\n\n### Other rules\n\nThere are two kinds of rules:\n\n- the base ones (`catalog/base/`),\n- as well as rules that only make sense for interactive use (`catalog/interactive/`).\n\nSome other available rules:\n\n- rewrite `(equal var nil)` to `(null var)`.\n- rewrite `(cl-fad:file-exists-p` or `(fad:file-exists-p` to using `uiop`.\n- rewrite `(funcall 'fn args)` to using a `#'` (respect lexical scope).\n- remove all `(log:debug …)`\n- check that `sort` is followed by `copy-seq` (WIP: we match the simplest expression of the form `(sort variable)`)\n\nYou can see `test/playground.lisp` for an overview of all available checks.\n\n\n## Installation\n\nClone this repository. You can use an alias to `colisper.sh`:\n\n    alias colisper=~/path/to/colisper/colisper.sh\n\n\n## Run all rules with a script\n\n    ./colisper.sh [--in-place] [--review] [file.lisp]\n\nBy default, only check the rules and print the diff on stdout.\n\nIf you don't give files as arguments, run the rules on all .lisp files of the current directory and its subdirectories.\n\nWith `--in-place`, write the changes to file (and indent them correctly with emacs).\n\nWith `--review` (`comby -review`), interactively accept or reject changes.\n\nIt returns 0 (success) if no rules were applied (code is good).\n\nTODO: write a solid script.\n\n### Run on a project\n\nTLDR;\n\n    cd src/ \u0026\u0026 colisper\n\nThis finds all `.lisp` files in subdirectories to run the Colisper rules on them.\n\nComby understands file extensions:\n\n    comby -config comby.toml -f .lisp\n\nbut it doesn't handle wildcards very well, so it's better to `cd` into\nyour source directory before running Comby/Colisper.\n\nMoreover:\n\n\u003e You can add additional flags, like -i, -exclude, -matcher and so on, as usual.\n\n\n## Emacs integration\n\nLoad `colisper.el`.\n\nCall `colisper-check-file`.\n\nCall a hydra, that gives you the choice of the rule:\n\n- `colisper-hydra/body`: asks wether to work on the current defun, the file, the project.\n- `colisper-[defun/file/project]-hydra/body`: act on the current defun/file/project, where the actions can be:\n  -`c`heck only: run all rules and display the diff in a compilation buffer,\n  - `r`un and apply the replace rule(s).\n    - this doesn't show much output, use your VCS (Magit) to see changes.\n\nOr call a rule directly. For example, place the cursor inside a\nfunction and call `M-x colisper--format-to-debug`. It replaces the\nfunction body with the new result.\n\n### Customization\n\nYou can customize the path to the catalog directory and use your own set of rules:\n\n    (setq colisper-catalog-path \"~/.config/colisper/catalog/\")\n\n\n## CHANGELOG\n\n- \u003c2025-01-18\u003e added `colisper-project-replace-all` and `colisper-hydra/body` that dispatches on defun/file/project.\n- initial POC\n\n## TODOs and ideas\n\n- [X] re-indent the file.\n\nComby doesn't respect indentation on rewriting, so we have to rely on another tool. We currently do with an `emacs --batch` command, and use the built-in `indent-region`.\n\n\u003e What is Comby not good at?\n\n\u003e When talking about matching or changing code, Comby is not well-suited to stylistic changes and formatting like \"insert a line break after 80 characters\". Pair Comby with a language-specific formatter to preserve formatting (like gofmt for the Go language) after performing a change.\n\nhttps://comby.dev/docs/faq\n\n- [X] interactively accept or reject changes (comby -review)\n  - done with the shell script (use `comby -review`), not on Emacs, but we can use Magit.\n\n- [X] differentiate rules that are made for live refactoring only, and rules for anti-pattern checks. =\u003e base/ and interactive/\n- [ ] differentiate rules for CL, Elisp and friends.\n\n\u003ca href='https://ko-fi.com/K3K828W0V' target='_blank'\u003e\u003cimg height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi2.png?v=2' border='0' alt='Buy Me a Coffee at ko-fi.com' title=\"Thanks!\"/\u003e\u003c/a\u003e\n\n## See also:\n\n- [trivial-formatter](https://github.com/hyotang666/trivial-formatter)\n- [lisp-critic](https://github.com/g000001/lisp-critic/)\n- [sblint](https://github.com/cxxxr/sblint)\n- [cl-indentify](https://github.com/yitzchak/cl-indentify/)\n- [comby.el](https://github.com/s-kostyaev/comby.el/), that asks rules interactively,\n\n\u003c!-- https://github.com/jonase/kibit,\n     https://github.com/brunchboy/kibit-helper/blob/master/kibit-helper.el\n     clojure tool, nice options. --\u003e\n\n## Final words\n\nThis method doesn't know about Lisp internals (the symbols' package and all). Work on SLIME to anable stuff like [this](https://github.com/slime/slime/issues/532) is still needed.\n\nLet's build something useful!\n\nThanks to Svetlyak40wt for [finding it out](https://github.com/svetlyak40wt/comby-test).\n\nLLGPLv3\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvindarel%2Fcolisper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvindarel%2Fcolisper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvindarel%2Fcolisper/lists"}