{"id":15010410,"url":"https://github.com/awkay/porting-tool","last_synced_at":"2026-03-15T19:41:43.906Z","repository":{"id":152984876,"uuid":"199121592","full_name":"awkay/porting-tool","owner":"awkay","description":"A generic tool for automating arbitrary transforms of  Clojure/Clojurescript/CLJC files ","archived":false,"fork":false,"pushed_at":"2020-04-13T15:55:08.000Z","size":158,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-26T08:36:15.926Z","etag":null,"topics":["clj","cljc","cljs","clojure","clojurescript","porting","refactoring"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":false,"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/awkay.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2019-07-27T05:41:17.000Z","updated_at":"2022-06-28T22:58:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"b6a82626-2215-4228-8b24-5a4c0c61f9d1","html_url":"https://github.com/awkay/porting-tool","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/awkay/porting-tool","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awkay%2Fporting-tool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awkay%2Fporting-tool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awkay%2Fporting-tool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awkay%2Fporting-tool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/awkay","download_url":"https://codeload.github.com/awkay/porting-tool/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awkay%2Fporting-tool/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30550574,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-15T15:03:43.933Z","status":"ssl_error","status_checked_at":"2026-03-15T15:03:37.630Z","response_time":61,"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":["clj","cljc","cljs","clojure","clojurescript","porting","refactoring"],"created_at":"2024-09-24T19:34:05.506Z","updated_at":"2026-03-15T19:41:43.888Z","avatar_url":"https://github.com/awkay.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"== Porting Tool\n\nWARNING: I've abandoned this effort. I ended up not wanting to \nspend the time refining it (I have enough jobs), and the\napproach didn't work very well (the zipper approach was very slow at\nruntime). Do not contact me about it, but feel free\nto look around or play with it. Don't remember if it compiles on the\ncurrent commit...you might have to back up a few.\n\nThis is a small library that can parse clj/cljs/cljc files and apply transforms.\nIt is not a full interpreter for Clojure, so it has limited understanding of the source, but it does understand namespaces and aliasing so that you can do the most common operations necessary for custom source refactorings.\n\nThe following transforms are supplied, but it is also easy to write you own:\n\n* Renaming artifact symbols that moved from one ns to another (including adding require with an alias, if necessary).\n* Rename namespaces in require clauses.\n\nThe tool supports CLJ, CLJS, and CLJC files with conditional reads.\n\n=== Intended Use\n\nThe original motivation for this library is porting Fulcro 2 to Fulcro 3.\nThe new version of that library created new namespaces for all APIs, changed some semantics, and a few API signatures.\nThe projects that use Fulcro use CLJ, CLJS, and CLJC files, and there were no tools I could find that would make it easy to do what I wanted: write generic transforms against forms where the handling of some of the grunt work was handled for me.\n\nThis library is intended to:\n\n* Make it possible for you to work with clj/cljs/cljc in a seamless but separately configurable way.\n* Comprehend the namespaces in a file and provide you with that information so you can more easily write transforms.\n* Traverse the forms in a contextual way (automatically handling reader conditionals) so that you can concentrate on just the actual code transform.\n\nUltimately this tools is just meant to be a very advanced form of `sed`:\nRun through a file a \"fix up\" the code.\n\nIf you're doing refactoring on your own source, and you're just working on CLJ and using emacs you should have a look at `clj-refactor`.\nIt does a number of refactorings that may be sufficient, but it doesn't support CLJS/CLJC (https://github.com/clojure-emacs/refactor-nrepl/issues/195).\n\nIntelliJ has a few minor refactorings that are more powerful for the specific things that they do (e.g. renaming a namespaced keyword), since that tool does try to comprehend your source code in a more complete manner.\n\n== Usage\n\nAdd the lastest git SHA to your `deps.edn` file.\n\n```\ncom.fulcrologic/porting-tool {:git/url \"https://github.com/fulcrologic/porting-tool.git\"\n                              :sha \"1d58d2d89f1704a29828fedc0f0d2be2f1573f55\"}}}\n```\n\nThen code a simple project file that can run the processing on files of your choice:\n\n```\n(ns my-porting-tool\n  (:require\n    [com.fulcrologic.porting.core :refer [process-file]]))\n\n(defn config {...}) ; see below\n\n(process-file \"sample.cljc\" config)\n```\n\nAt the moment this will output to `*out*` (stdout by default) using `println`\nand `pprint`.\n\n=== Configuration\n\nYou must supply a configuration that describes what you want done to the file(s) that are processed.\nYou must understand that the tool needs to know configuration for each feature (language) that will be affected, so the configuration file has a section for each:\n\n```\n{:clj {:transforms       [...]\n       :let-forms        #{encore/if-let}\n       :defn-forms       #{'com.fulcrologic.fulcro.components/defsc 'ghostwheel.core/\u003edefn}\n       :namespace-\u003ealias {'com.fulcrologic.fulcro.components 'comp\n                          'com.fulcrologic.fulcro.dom        'dom\n                          'com.fulcrologic.fulcro.dom-server 'dom}}\n```\n\nThe `:transforms` is a vector of tuples `[predicate transform]` that are to be performed.\nThe rest of the keys in the config are dictated by these transforms, but many of them can create new requires in the file.\nThe\n`:namespace-\u003ealias` map indicates what alias is preferred for new namespaces.\n\nThe `:let-forms` and `defn-forms` tell the tool what macros you have that should be seen as let-like or defn-like things, so that potential shadowing and renaming problems can be properly detected.\nThe tool has a built-in list of these, so you only need to configure them if you have extra ones (and the defaults are always merged into whatever you configure).\n\n==== CLJC\n\nWorking with CLJC files is a bit more work.\nA CLJC file (for our purposes) really is three things in one: forms for *just* CLJ, forms for *just* CLJS, and forms that are \"language agnostic\".\n\nWhen a transform is given a form it will also be told the \"feature context\".\nThe transform will use that to figure out which of the configurations to apply to the form it is working on.\nThe feature contexts are `:clj`, `cljs`, and `:agnostic`; therefore, a configuration for a CLJC transform will commonly look something like this:\n\n```\n(let [base {:fqname-old-\u003enew  {'fulcro.client.primitives/defsc        'com.fulcrologic.fulcro.components/defsc\n                               'fulcro.client.primitives/get-computed 'com.fulcrologic.fulcro.components/get-computed}\n            :transforms       [rename/rename-artifacts-transform\n                               rename/rename-namespaces-transform\n                               rename/add-missing-namespaces-transform]\n            :namespace-\u003ealias {'com.fulcrologic.fulcro.components 'comp\n                               'com.fulcrologic.fulcro.dom        'dom\n                               'com.fulcrologic.fulcro.dom-server 'dom}}\n      config {:agnostic base\n              :cljs     (merge base {:namespace-old-\u003enew {'fulcro.client.dom 'com.fulcrologic.fulcro.dom}})\n              :clj      (merge base {:namespace-old-\u003enew {'fulcro.client.dom-server 'com.fulcrologic.fulcro.dom-server}})}]\n    ...)\n```\n\nNOTE: the `:transforms` themselves must be configured for each feature of a file, since some transforms may only make sense for a given aspect of the file.\n\n== Transforms\n\nThis library's implementation provides the core tools: namespace comprehension and traversal of forms in a language feature-sensitive manner.\nThis makes is easy to write transforms.\n\nIt is important to understand that this tool is *not* a global refactoring tool that could actually move an artifact from one disk file to another.\nIt is local transforms on *one file at a time* where you can indicate changes you'd like to make to the *forms* within that file.\n\nAs such, when we speak of a \"rename\" we are almost always referring to the \"global name\" of some artifact (e.g. its fully-qualified name).\n\n=== How the Processing Works\n\nThe processing of a file is done in two phases:\n\n- Phase 1:\nAll forms are traversed.\nAll transforms are invoked.\nA processing environment (`env`) is passed to transforms and will include a `:state-atom` holding a map that transforms can use to \"save information\" about the things they see.\nThis is useful for doing things like gathering up a list of namespaces that are used so that the namespace form can be fixed on the second pass.\n- Phase 2:\nIdentical to 1 except the `env` now includes `:state`, which is cumulative result of what was in the `:state-atom` at then end of phase 1.\n\nEach phase does the same steps (some of which have multiple passes):\n\n* Analyzes the ns form for each feature (e.g. :clj, :cljs, etc) that is necessary for the file.\nIt records what namespaces are required in the file, and what symbols are referred (aliased to simple symbols).\nThe result of this step becomes the *parsing environment*.\n* Forms are traversed recursively, but in a \"context sensitive\" manner (one pass for each feature of the file).\nTransforms only see forms for the a single feature context at a time.\nFor example if the source had `#?(:clj a :cljs b)`\nand you were in the `:clj` context, the transform function would only see `a`, and whatever it returned would only *affect* the CLJ side of the reader conditional.\nThe `:agnostic` feature pass *skips* reader conditionals altogether.\n** `let`-like and `defn`-like forms are analyzed for possible naming confusion, and are used to modify the parsing environment and issue warnings.\nAny local symbol bindings will remove conflicting namespace `refer`s, but since\ncode comprehension is not part of this library's purpose it will just issue warnings when that might result in\na problem with the output.\n* Transforms are applied in order for each form.\n\nNOTE: CLJC files require some care.\nThe :clj, :cljs, *and* :agnostic feature passes will see the same (non-conditional) form.\nIdeally, only the agnostic transform would be configured to respond for that form (or all feature configs would be configured identically for it).\nA transform *is allowed* to output a Reader Conditional (TODO: document how to do that), which means a transform could convert\nsomething from language agnostic to conditional.\n\n=== The Transform `env`\n\nYour transform processing `env` will include a number of useful things:\n\n`:parsing-envs`:: A map from feature key (e.g. :clj) to the `parsing-env` for the features of the current file.\n`:zloc`:: A current rewrite-clj zipper set to the location of the form being processed.\n`:config`:: The map from feature to config that you supplied on start.\n`:feature-context`:: The current feature being processed.\n`:current-ns`:: The name of ns of the file being processed.\n\nEach `parsing-env` will include feature-specific details of the namespace:\n`:nsalias-\u003ens`:: A map from namespace aliases to the real namespace (from the `:as` clauses in the requires).\nIf there is no alias for a ns it will still be listed as itself.\n`:ns-\u003ealias`:: A reverse of from ns to its alias.\nAll nses are included (e.g. no alias will have same k as v).\n`:raw-sym-\u003efqsym`:: A map from raw symbols to their fully-qualified name (from the `:refer` clauses in the requires)\n\n=== Reporting Problems\n\nSometimes there is no transform possible and you just need to inform the user that there is a problem.\nThe\n`com.fulcrologic.porting.parsing.util/report-warning!` and\n`com.fulcrologic.porting.parsing.util/report-error!` functions should be used for this.\nThe latter throws an exception to halt processing.\nThey will include the file and line for you as a prefix to your message.\n\n=== Writing Your Own Transform\n\nSee the source of the built-in transforms for some examples of how to write them.\n\n=== Built-in Transforms\n\n=== Function Rename\n\nSee the docstring of `com.fulcrologic.porting.transforms.rename/rename-artifacts-transform` for usage.\n\nSay the function `some.lib/f` is moved and renamed to `other.thing/g`:\n\nYour old file might be:\n\n```\n(ns my.thing\n  (:require\n    [some.lib :as lib :refer [f]]))\n\n(lib/f)\n(f)\n```\n\nand the desired new file would be:\n\n```\n(ns my.thing\n  (:require\n    [other.thing :as thing]))\n\n(thing/g)\n(thing/g)\n```\n\n=== Adding Missing Namespaces\n\nThis transform is a companion of the `rename-artifacts-transform` (which must appear *before* it).\n\nSee the docstring of `com.fulcrologic.porting.transforms.rename/add-missing-namespaces` for usage.\n\n=== Renaming Namespaces\n\nSometimes the only real change is that of the namespace itself.\nYou could (tediously) list out every single function from the old to the new namespace in the artifact renaming, but in the case of a simple namespace rename this is overkill.\n\nSee the docstring of `com.fulcrologic.porting.transforms.rename/rename-namespaces-transform` for usage.\n\n== Limitations\n\nThis library is *not* a full compiler, and as such it cannot possibly comprehend your code.\nClojure(script) macros can create bindings that *should* shadow namespace aliases, but this library has limited support for figuring out when shadowing is happening.\n\nIf you have a macro that behaves like `defn` or `let` you should configure it as described above.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fawkay%2Fporting-tool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fawkay%2Fporting-tool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fawkay%2Fporting-tool/lists"}