├── .clj-kondo ├── config.edn └── imports │ ├── babashka │ └── fs │ │ └── config.edn │ ├── etaoin │ └── etaoin │ │ ├── config.edn │ │ └── etaoin │ │ ├── api.clj_kondo │ │ ├── api2.clj_kondo │ │ └── hooks_util.clj_kondo │ ├── http-kit │ └── http-kit │ │ ├── config.edn │ │ └── httpkit │ │ └── with_channel.clj │ ├── lread │ └── status-line │ │ └── config.edn │ ├── rewrite-clj │ └── rewrite-clj │ │ └── config.edn │ └── taoensso │ └── encore │ ├── config.edn │ └── taoensso │ └── encore.clj ├── .codecov.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── docs.md │ └── feature_request.md └── workflows │ ├── code-coverage.yml │ ├── libs-test.yml │ ├── native-image-test.yml │ ├── publish.yml │ ├── setup-planck │ └── action.yml │ ├── shared-setup │ └── action.yml │ └── unit-test.yml ├── .gitignore ├── CHANGELOG.adoc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── ORIGINATOR ├── README.adoc ├── bb.edn ├── build.clj ├── build └── build_shared.clj ├── deps.edn ├── doc ├── 01-user-guide.adoc ├── 02-developer-guide.adoc ├── 03-faq.adoc ├── 04-maintainer-guide.adoc ├── cljdoc.edn ├── contributors.edn ├── design │ ├── 01-merging-rewrite-clj-and-rewrite-cljs.adoc │ ├── custom-node-skipping.adoc │ └── namespaced-elements.adoc ├── diff-notes │ ├── rewrite-clj-v0-lang-clj-and-rewrite-clj-v1-lang-clj.adoc │ ├── rewrite-clj-v0-lang-clj-and-rewrite-cljs-lang-cljs.adoc │ ├── rewrite-clj-v1-lang-cljs-and-rewrite-clj-v1-lang-clj-documented-only.adoc │ ├── rewrite-clj-v1-lang-cljs-and-rewrite-clj-v1-lang-clj.adoc │ └── rewrite-cljs-lang-cljs-and-rewrite-clj-v1-lang-cljs.adoc ├── generated │ └── contributors │ │ ├── AndreaCrotti.png │ │ ├── FiV0.png │ │ ├── NoahTheDuke.png │ │ ├── PEZ.png │ │ ├── RickMoynihan.png │ │ ├── SevereOverfl0w.png │ │ ├── anmonteiro.png │ │ ├── arrdem.png │ │ ├── awb99.png │ │ ├── bbatsov.png │ │ ├── bobbicodes.png │ │ ├── borkdude.png │ │ ├── brian-dawn.png │ │ ├── danielcompton.png │ │ ├── doby162.png │ │ ├── drorbemet.png │ │ ├── eraserhd.png │ │ ├── ericdallo.png │ │ ├── expez.png │ │ ├── fazzone.png │ │ ├── ferdinand-beyer.png │ │ ├── frenchy64.png │ │ ├── green-coder.png │ │ ├── guoyongxin.png │ │ ├── ikappaki.png │ │ ├── immoh.png │ │ ├── ivarref.png │ │ ├── jespera.png │ │ ├── kkinnear.png │ │ ├── lread.png │ │ ├── luxbock.png │ │ ├── mainej.png │ │ ├── martinklepsch.png │ │ ├── matanox.png │ │ ├── mhuebert.png │ │ ├── mikekap.png │ │ ├── mjayprateek.png │ │ ├── mrkam2.png │ │ ├── msgodf.png │ │ ├── mynomoto.png │ │ ├── optevo.png │ │ ├── p4ulcristian.png │ │ ├── plexus.png │ │ ├── rfhayashi.png │ │ ├── rgkirch.png │ │ ├── rundis.png │ │ ├── shaunlebron.png │ │ ├── shaunxcode.png │ │ ├── shmish111.png │ │ ├── slipset.png │ │ ├── snoe.png │ │ ├── sogaiu.png │ │ ├── stathissideris.png │ │ ├── swannodette.png │ │ ├── theronic.png │ │ ├── vemv.png │ │ ├── weavejester.png │ │ ├── xsc.png │ │ └── zcaudate.png ├── introduction-parsed-nodes.drawio ├── introduction-parsed-nodes.png ├── map-nodes.drawio ├── map-nodes.png ├── rewrite-clj-logo.png └── rewrite-clj-logo.svg ├── fig.cljs.edn ├── package-lock.json ├── package.json ├── resources └── clj-kondo.exports │ └── rewrite-clj │ └── rewrite-clj │ └── config.edn ├── script ├── apply_import_vars.clj ├── ci_publish.clj ├── ci_unit_tests.clj ├── clj_graal │ ├── gen_test_runner.clj │ └── template │ │ ├── by_ns_test_runner.clj.template │ │ ├── by_var_test_runner.clj.template │ │ └── direct_runner.clj.template ├── cljdoc_preview.clj ├── code_info │ └── ns_lister.clj ├── dev_repl.clj ├── doc_update_readme.clj ├── download_deps.clj ├── gen-user-guide-reader-macro-table.clj ├── helper │ ├── deps_patcher.clj │ ├── env.clj │ ├── graal.clj │ ├── jdk.clj │ ├── main.clj │ ├── os.clj │ └── shell.clj ├── lint.clj ├── lint_eastwood.clj ├── lint_kondo.clj ├── lread │ └── apply_import_vars.clj ├── outdated.clj ├── publish.clj ├── resources │ └── logback.xml ├── sci_test_gen_publics.clj ├── sci_test_runner.clj ├── test_clj.clj ├── test_clj_watch.clj ├── test_cljs.clj ├── test_cljs_watch.clj ├── test_coverage.clj ├── test_doc.clj ├── test_jvm_sci.clj ├── test_libs.clj ├── test_native.clj ├── test_native_sci.clj └── test_shadow_cljs.clj ├── shadow-cljs.edn ├── src ├── rewrite_clj.cljc └── rewrite_clj │ ├── custom_zipper │ ├── core.cljc │ ├── switchable.cljc │ └── utils.cljc │ ├── interop.cljc │ ├── node.cljc │ ├── node │ ├── coercer.cljc │ ├── comment.cljc │ ├── extras.cljc │ ├── fn.cljc │ ├── forms.cljc │ ├── integer.cljc │ ├── keyword.cljc │ ├── meta.cljc │ ├── namespaced_map.cljc │ ├── protocols.cljc │ ├── quote.cljc │ ├── reader_macro.cljc │ ├── regex.cljc │ ├── seq.cljc │ ├── string.clj │ ├── stringz.cljc │ ├── token.cljc │ ├── uneval.cljc │ └── whitespace.cljc │ ├── paredit.cljc │ ├── parser.cljc │ ├── parser │ ├── core.cljc │ ├── impl.cljc │ ├── keyword.cljc │ ├── namespaced_map.cljc │ ├── string.cljc │ ├── token.cljc │ └── whitespace.cljc │ ├── reader.cljc │ ├── zip.cljc │ └── zip │ ├── base.cljc │ ├── context.cljc │ ├── edit.clj │ ├── editz.cljc │ ├── find.clj │ ├── findz.cljc │ ├── insert.cljc │ ├── move.cljc │ ├── options.cljc │ ├── remove.clj │ ├── removez.cljc │ ├── seq.clj │ ├── seqz.cljc │ ├── subedit.cljc │ ├── walk.cljc │ └── whitespace.cljc ├── template └── rewrite_clj │ ├── node.cljc │ ├── node │ └── string.clj │ ├── zip.cljc │ └── zip │ ├── edit.clj │ ├── find.clj │ ├── remove.clj │ └── seq.clj ├── test-isolated └── rewrite_clj │ └── zip_solo_test.cljc ├── test-resources └── code-samples │ └── clojurescript │ └── fa4b8d853be08120cb864782e4ea48826b9d757e │ └── src │ └── main │ ├── cljs │ └── cljs │ │ └── core.cljs │ └── clojure │ └── cljs │ └── analyzer.cljc ├── test └── rewrite_clj │ ├── custom_zipper │ ├── core_test.cljc │ └── utils_test.cljc │ ├── examples │ └── cljx_test.cljc │ ├── hello_figwheel.cljs │ ├── node │ ├── coercer_test.cljc │ ├── equals_test.cljc │ ├── generators.cljc │ ├── integer_test.cljc │ └── node_test.cljc │ ├── node_test.cljc │ ├── paredit_test.cljc │ ├── parser_test.cljc │ ├── regression_test.cljc │ ├── transform_test.cljc │ ├── zip │ ├── base_test.cljc │ ├── editz_test.cljc │ ├── findz_test.cljc │ ├── insert_test.cljc │ ├── move_test.cljc │ ├── removez_test.cljc │ ├── seqz_test.cljc │ ├── subedit_test.cljc │ ├── test_helper.cljc │ ├── walk_test.cljc │ └── whitespace_test.cljc │ └── zip_test.cljc └── tests.edn /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:config-paths ^:replace ["lread/status-line"] 2 | :lint-as 3 | { clojure.test.check.clojure-test/defspec clojure.core/def 4 | clojure.test.check.properties/for-all clojure.core/let 5 | rewrite-clj.zip/subedit-> clojure.core/-> 6 | rewrite-clj.zip.subedit/subedit-> clojure.core/-> 7 | rewrite-clj.zip/subedit->> clojure.core/->> 8 | rewrite-clj.zip.subedit/subedit->> clojure.core/->> 9 | rewrite-clj.zip/edit-> clojure.core/-> 10 | rewrite-clj.zip.subedit/edit-> clojure.core/-> 11 | rewrite-clj.zip/edit->> clojure.core/->> 12 | rewrite-clj.zip.subedit/edit->> clojure.core/->> 13 | rewrite-clj.custom-zipper.switchable/defn-switchable clojure.core/defn} 14 | :linters 15 | {:discouraged-var {clojure.test/are {:message "We're not fans of clojure.test/are, use doseq instead"}} 16 | :redundant-str-call {:level :warning} 17 | :redundant-fn-wrapper {:level :warning} 18 | :redundant-ignore {:level :warning} 19 | :unused-value {:level :warning} 20 | :aliased-namespace-symbol {:level :warning} 21 | :unknown-require-option {:level :off} ;; overcome a wee bug in 2020-10-05 version 22 | :unsorted-required-namespaces {:level :warning} 23 | :unresolved-var {:exclude [io.aviso.ansi]} 24 | :unused-namespace 25 | {:exclude [clojure.test.check]} 26 | :unused-referred-var 27 | {:exclude {clojure.test.check [quick-check]}} 28 | :deprecated-var 29 | {:exclude {rewrite-clj.paredit/slurp-forward {:namespaces [rewrite-clj.paredit-test]} 30 | rewrite-clj.paredit/slurp-forward-fully {:namespaces [rewrite-clj.paredit-test]} 31 | rewrite-clj.paredit/slurp-backward {:namespaces [rewrite-clj.paredit-test]} 32 | rewrite-clj.paredit/slurp-backward-fully {:namespaces [rewrite-clj.paredit-test]} 33 | 34 | rewrite-clj.zip.base/->string 35 | {:namespaces [rewrite-clj.zip]} 36 | rewrite-clj.zip.base/->root-string 37 | {:namespaces [rewrite-clj.zip]} 38 | rewrite-clj.zip.base/value 39 | {:namespaces [rewrite-clj.zip]} 40 | rewrite-clj.zip/->root-string 41 | {:namespaces [rewrite-clj.regression-test]} 42 | rewrite-clj.zip/->string 43 | {:namespaces [rewrite-clj.regression-test]} 44 | 45 | rewrite-clj.zip/append-space 46 | {:namespaces [rewrite-clj.zip.whitespace-test]} 47 | rewrite-clj.zip.whitespace/append-space 48 | {:namespaces [rewrite-clj.zip]} 49 | 50 | rewrite-clj.zip/prepend-space 51 | {:namespaces [rewrite-clj.zip.whitespace-test]} 52 | rewrite-clj.zip.whitespace/prepend-space 53 | {:namespaces [rewrite-clj.zip]} 54 | 55 | rewrite-clj.zip/append-newline 56 | {:namespaces [rewrite-clj.zip.whitespace-test]} 57 | rewrite-clj.zip.whitespace/append-newline 58 | {:namespaces [rewrite-clj.zip]} 59 | 60 | rewrite-clj.zip/prepend-newline 61 | {:namespaces [rewrite-clj.zip.whitespace-test]} 62 | rewrite-clj.zip.whitespace/prepend-newline 63 | {:namespaces [rewrite-clj.zip]} 64 | 65 | rewrite-clj.node.protocols/value 66 | {:namespaces [rewrite-clj.node rewrite-clj.zip.base]} 67 | rewrite-clj.node/value 68 | {:defs [rewrite-clj.zip.base/value]} 69 | 70 | rewrite-clj.zip/edn 71 | {:namespaces [rewrite-clj.zip-test]} 72 | 73 | rewrite-clj.zip/edn* 74 | {:namespaces [;; this is a bb script usage which is using an older version rewrite-clj 75 | ;; can update code and remove when bb upgrades to rewrite-clj v1.1 76 | helper.deps-patcher 77 | ;; and this is legitimate, we still need to sanity test that the deprecated fn works 78 | rewrite-clj.zip-test]}}}}} 79 | -------------------------------------------------------------------------------- /.clj-kondo/imports/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/imports/etaoin/etaoin/config.edn: -------------------------------------------------------------------------------- 1 | {:linters 2 | {:etaoin/with-x-action {:level :error} 3 | :etaoin/binding-sym {:level :error} 4 | :etaoin/opts-map-type {:level :error} 5 | :etaoin/opts-map-pos {:level :error} 6 | :etaoin/empty-opts {:level :warning}} 7 | :hooks 8 | {:analyze-call 9 | {etaoin.api/with-chrome etaoin.api/with-browser 10 | etaoin.api/with-chrome-headless etaoin.api/with-browser 11 | etaoin.api/with-firefox etaoin.api/with-browser 12 | etaoin.api/with-firefox-headless etaoin.api/with-browser 13 | etaoin.api/with-edge etaoin.api/with-browser 14 | etaoin.api/with-edge-headless etaoin.api/with-browser 15 | etaoin.api/with-safari etaoin.api/with-browser 16 | 17 | etaoin.api/with-driver etaoin.api/with-driver 18 | etaoin.api/with-key-down etaoin.api/with-key-down 19 | etaoin.api/with-pointer-btn-down etaoin.api/with-pointer-btn-down 20 | 21 | ;; api2 moves to a more conventional let-ish vector syntax 22 | etaoin.api2/with-chrome etaoin.api2/with-browser 23 | etaoin.api2/with-chrome-headless etaoin.api2/with-browser 24 | etaoin.api2/with-edge etaoin.api2/with-browser 25 | etaoin.api2/with-edge-headless etaoin.api2/with-browser 26 | etaoin.api2/with-firefox etaoin.api2/with-browser 27 | etaoin.api2/with-firefox-headless etaoin.api2/with-browser 28 | etaoin.api2/with-safari etaoin.api2/with-browser}} 29 | :lint-as 30 | {etaoin.api/with-pointer-left-btn-down clojure.core/->}} 31 | -------------------------------------------------------------------------------- /.clj-kondo/imports/etaoin/etaoin/etaoin/api2.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns etaoin.api2 2 | (:require [clj-kondo.hooks-api :as api] 3 | [etaoin.hooks-util :as h])) 4 | 5 | (defn with-browser 6 | "Newer variants for api2 7 | [[bind & [options]] & body]" 8 | [{:keys [node]}] 9 | (let [macro-args (rest (:children node)) 10 | binding-like-vector (first macro-args)] 11 | (if-not (api/vector-node? binding-like-vector) 12 | ;; could use clj-kondo findings, but I think this is good for now 13 | (throw (ex-info "Expected vector for first arg" {})) 14 | (let [binding-sym (-> binding-like-vector :children first)] 15 | (if-not (h/symbol-node? binding-sym) 16 | (throw (ex-info "Expected binding symbol for first arg in vector" {})) 17 | (let [other-args (rest binding-like-vector) 18 | body (rest macro-args)] 19 | {:node (api/list-node 20 | (list* 21 | (api/token-node 'let) 22 | ;; simulate the effect, macro is creating a new thing (driver for example) 23 | ;; via binding it. I don't think the bound value matters for the linting process 24 | (api/vector-node [binding-sym (api/map-node [])]) 25 | ;; reference the other args so that they are not linted as unused 26 | (api/vector-node other-args) 27 | body))})))))) 28 | -------------------------------------------------------------------------------- /.clj-kondo/imports/etaoin/etaoin/etaoin/hooks_util.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns etaoin.hooks-util 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn symbol-node? [node] 5 | (and (api/token-node? node) 6 | (symbol? (api/sexpr node)))) 7 | -------------------------------------------------------------------------------- /.clj-kondo/imports/http-kit/http-kit/config.edn: -------------------------------------------------------------------------------- 1 | 2 | {:hooks 3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/imports/http-kit/http-kit/httpkit/with_channel.clj: -------------------------------------------------------------------------------- 1 | (ns httpkit.with-channel 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn with-channel [{node :node}] 5 | (let [[request channel & body] (rest (:children node))] 6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {}))) 7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {}))) 8 | (let [new-node 9 | (api/list-node 10 | (list* 11 | (api/token-node 'let) 12 | (api/vector-node [channel (api/vector-node [])]) 13 | request 14 | body))] 15 | 16 | {:node new-node}))) 17 | -------------------------------------------------------------------------------- /.clj-kondo/imports/lread/status-line/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {lread.status-line/line clojure.tools.logging/infof 2 | lread.status-line/die clojure.tools.logging/infof}} 3 | -------------------------------------------------------------------------------- /.clj-kondo/imports/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/imports/taoensso/encore/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks 2 | {:analyze-call 3 | {taoensso.encore/defalias taoensso.encore/defalias 4 | taoensso.encore/defn-cached taoensso.encore/defn-cached 5 | taoensso.encore/defonce taoensso.encore/defonce}}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/imports/taoensso/encore/taoensso/encore.clj: -------------------------------------------------------------------------------- 1 | (ns taoensso.encore 2 | "I don't personally use clj-kondo, so these hooks are 3 | kindly authored and maintained by contributors. 4 | PRs very welcome! - Peter Taoussanis" 5 | (:refer-clojure :exclude [defonce]) 6 | (:require 7 | [clj-kondo.hooks-api :as hooks])) 8 | 9 | (defn defalias 10 | [{:keys [node]}] 11 | (let [[sym-raw src-raw] (rest (:children node)) 12 | src (or src-raw sym-raw) 13 | sym (if src-raw sym-raw (symbol (name (hooks/sexpr src))))] 14 | {:node 15 | (with-meta 16 | (hooks/list-node 17 | [(hooks/token-node 'def) 18 | (hooks/token-node (hooks/sexpr sym)) 19 | (hooks/token-node (hooks/sexpr src))]) 20 | (meta src))})) 21 | 22 | (defn defn-cached 23 | [{:keys [node]}] 24 | (let [[sym _opts binding-vec & body] (rest (:children node))] 25 | {:node 26 | (hooks/list-node 27 | (list 28 | (hooks/token-node 'def) 29 | sym 30 | (hooks/list-node 31 | (list* 32 | (hooks/token-node 'fn) 33 | binding-vec 34 | body))))})) 35 | 36 | (defn defonce 37 | [{:keys [node]}] 38 | ;; args = [sym doc-string? attr-map? init-expr] 39 | (let [[sym & args] (rest (:children node)) 40 | [doc-string args] (if (and (hooks/string-node? (first args)) (next args)) [(hooks/sexpr (first args)) (next args)] [nil args]) 41 | [attr-map init-expr] (if (and (hooks/map-node? (first args)) (next args)) [(hooks/sexpr (first args)) (fnext args)] [nil (first args)]) 42 | 43 | attr-map (if doc-string (assoc attr-map :doc doc-string) attr-map) 44 | sym+meta (if attr-map (with-meta sym attr-map) sym) 45 | rewritten 46 | (hooks/list-node 47 | [(hooks/token-node 'clojure.core/defonce) 48 | sym+meta 49 | init-expr])] 50 | 51 | {:node rewritten})) 52 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | 10 | comment: false -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners are auto-invited to review PRs including files matching specified pattern(s). 2 | # We opt out of this by only matching the CODEOWNERS file itself. 3 | .github/CODEOWNERS @lread @slipset @borkdude 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Version** 11 | [Please specify what version of rewrite-clj you are using. If you are referencing via `:git/url`, specify the `:sha`] 12 | 13 | **Platform** 14 | [Please specificy relevant platforms] 15 | Operating System: [os and version] 16 | Clojure version: [clj version] 17 | JDK vendor and version: [jdk vendor and version] 18 | 19 | ClojureScript 20 | - version: [cljs version] 21 | - tooling: [shadow-cljs|cljs|etc] 22 | - host: [node|firefox|chrome|planck|etc] 23 | 24 | **Symptom** 25 | [As succinctly as you can, please describe the symptom. If you have some thoughts on what the problem may be you can write about that below under the "Diagnosis" section] 26 | 27 | **Reproduction** 28 | [Please provide a minimal working reproduction of the symptom] 29 | 30 | **Actual behavior** 31 | [Describe the unexpected behavior] 32 | 33 | **Expected behavior** 34 | [Describe what you'd expect to happen instead] 35 | 36 | **Diagnosis** 37 | [Have you diagnosed why the issue is occurring? That's great, thanks for diving in, please jot your findings down here] 38 | 39 | **Action** 40 | [What's your next step? Are you going to pitch in with a PR? If so, wonderful, let us know your plans! If not, that's fine too, we very much appreciate the time you took to describe the bug you encountered!] 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs 3 | about: Docs 4 | title: '' 5 | labels: docs 6 | assignees: '' 7 | 8 | --- 9 | 10 | [Either you found a problem in rewrite-clj docs or you have read the docs and are still unsure how to proceed. Alternatively you can visit the [Clojurians Slack #rewrite-clj channel](https://clojurians.slack.com/messages/CHB5Q2XUJ) to chat.] 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Problem/Opportunity** 11 | [Remember that one of rewrite-clj v1's primary goals is to not break API compatibility. With that understanding, what problem are you trying to solve? Or what opportunity do you want to take advantage of? Who will it help? Please write up a clear and concise description here] 12 | 13 | **Proposed Solution** 14 | [A clear and concise description of what you want to happen.] 15 | 16 | **Alternative Solutions** 17 | [A clear and concise description of any alternative solutions or features you've considered.] 18 | 19 | **Additional context** 20 | [Add other context, if any, about the feature request here.] 21 | 22 | **Action** 23 | [What's your next step? Do you want to be involved with designing, coding and then maintaining a solution, if so let us know! If not, that's fine too, thanks for sharing your idea!] 24 | -------------------------------------------------------------------------------- /.github/workflows/code-coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | code-coverage: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup 17 | uses: ./.github/workflows/shared-setup 18 | 19 | - name: Run Tests with Code Coverage 20 | run: bb test-coverage 21 | 22 | - name: Upload Code Coverage Results 23 | uses: codecov/codecov-action@v5 24 | with: 25 | fail_ci_if_error: true # optional (default = false) 26 | files: ./target/coverage/codecov.json 27 | token: ${{ secrets.CODECOV_TOKEN }} # required 28 | verbose: true # optional (default = false) 29 | -------------------------------------------------------------------------------- /.github/workflows/libs-test.yml: -------------------------------------------------------------------------------- 1 | name: Libs Test 2 | 3 | on: 4 | # allow this workflow to be called from other workflows, namely: publish 5 | workflow_call: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | enumerate-libs: 15 | runs-on: ubuntu-latest 16 | 17 | outputs: 18 | libs: ${{ steps.set-libs.outputs.libs }} 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup 25 | uses: ./.github/workflows/shared-setup 26 | 27 | - id: set-libs 28 | name: Set libs var for matrix 29 | ## call bb script directly instead of as task to avoid task status output 30 | run: echo "libs=$(bb script/test_libs.clj list --format=json)" >> $GITHUB_OUTPUT 31 | 32 | libs-test: 33 | needs: enumerate-libs 34 | runs-on: ubuntu-latest 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | include: ${{ fromJSON(needs.enumerate-libs.outputs.libs) }} 39 | 40 | name: ${{ matrix.lib-name }} 41 | 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | with: 46 | fetch-depth: 0 47 | 48 | - name: Setup 49 | uses: ./.github/workflows/shared-setup 50 | with: 51 | clj-cache-prefix: clj-libs-deps-${{ matrix.lib-name }} 52 | clj-cache-hash-files: "'script/test_libs.clj','deps.edn','bb.edn'" 53 | 54 | - name: Install Lein 55 | uses: DeLaGuardo/setup-clojure@13.2 56 | with: 57 | lein: 2.11.2 58 | 59 | - name: Install Planck 60 | uses: ./.github/workflows/setup-planck 61 | if: contains( matrix.requires, 'planck' ) 62 | 63 | - name: Run Libs Tests 64 | run: bb test-libs run ${{ matrix.lib-name }} 65 | -------------------------------------------------------------------------------- /.github/workflows/native-image-test.yml: -------------------------------------------------------------------------------- 1 | name: Native Image Tests 2 | 3 | on: 4 | # allow this workflow to be called from other workflows, namely: publish 5 | workflow_call: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | build: 15 | runs-on: ${{ matrix.os }}-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ windows, ubuntu, macos ] 20 | java-version: [ '24' ] 21 | test: [ native, native-sci ] 22 | clojure-version: [ '1.12' ] 23 | 24 | name: ${{ matrix.os }},jdk${{ matrix.java-version }},${{ matrix.test }},clj${{ matrix.clojure-version }} 25 | 26 | steps: 27 | # 28 | # Tell git not to convert newlines on checkout for Windows 29 | # 30 | - name: Prepare git (Windows) 31 | run: git config --global core.autocrlf false 32 | if: matrix.os == 'windows' 33 | 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Setup 38 | uses: ./.github/workflows/shared-setup 39 | with: 40 | jdk: skip 41 | 42 | - name: Install GraalVM 43 | uses: graalvm/setup-graalvm@v1 44 | with: 45 | java-version: ${{ matrix.java-version }} 46 | distribution: 'graalvm-community' 47 | github-token: ${{ secrets.GITHUB_TOKEN }} 48 | 49 | # 50 | # native image tests 51 | # 52 | - name: Execute test-${{ matrix.test }} 53 | run: bb test-${{ matrix.test }} --clojure-version ${{ matrix.clojure-version }} 54 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | tags: 5 | - 'v\d+.*' 6 | 7 | jobs: 8 | unit-test: 9 | uses: ./.github/workflows/unit-test.yml 10 | native-image-test: 11 | uses: ./.github/workflows/native-image-test.yml 12 | libs-test: 13 | uses: ./.github/workflows/libs-test.yml 14 | 15 | publish: 16 | environment: publish 17 | runs-on: ubuntu-latest 18 | needs: [unit-test, native-image-test, libs-test] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup 25 | uses: ./.github/workflows/shared-setup 26 | 27 | - name: Deploy to clojars 28 | env: 29 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 30 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 31 | run: bb -ci-clojars-deploy 32 | 33 | - name: Create GitHub Release 34 | env: 35 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | run: bb -ci-github-create-release 37 | 38 | - name: Inform Cljdoc 39 | run: bb -ci-cljdoc-request-build 40 | -------------------------------------------------------------------------------- /.github/workflows/setup-planck/action.yml: -------------------------------------------------------------------------------- 1 | name: 'setup plank' 2 | description: 'Composite action to install planck (no-op if invoked for Windows)' 3 | inputs: 4 | shell: 5 | description: 'shell must be specified for composite actions - so we setup an appropriate default' 6 | required: false 7 | default: ${{ contains(runner.os, 'windows') && 'pwsh' || 'bash' }} 8 | 9 | runs: 10 | using: 'composite' 11 | 12 | steps: 13 | - name: Install planck (macOS) 14 | shell: ${{ inputs.shell }} 15 | run: brew install planck 16 | if: runner.os == 'macOS' 17 | 18 | - name: Install planck (linux) 19 | shell: ${{ inputs.shell }} 20 | run: | 21 | # There are not planck binaries for jammy yet, so hack-around to use focal release 22 | sudo add-apt-repository -y "deb http://cz.archive.ubuntu.com/ubuntu focal main universe" 23 | sudo add-apt-repository -y "deb http://security.ubuntu.com/ubuntu focal-security main" 24 | 25 | # is missing after installing planck so compensate 26 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libicu66 libjavascriptcoregtk-4.0-18 27 | 28 | wget https://launchpad.net/~mfikes/+archive/ubuntu/planck/+files/planck_2.25.0-1ppa1~focal1_amd64.deb 29 | sudo apt-get install ./planck_2.25.0-1ppa1~focal1_amd64.deb 30 | if: runner.os == 'Linux' 31 | 32 | - name: Planck version (macos, linux) 33 | shell: ${{ inputs.shell }} 34 | run: | 35 | echo "planck --version" 36 | planck --version 37 | if: runner.os != 'Windows' 38 | -------------------------------------------------------------------------------- /.github/workflows/shared-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: 'shared setup' 2 | description: 'Composite action for common setup steps' 3 | inputs: 4 | jdk: 5 | description: 'jdk major version, use value skip to not install jdk' 6 | required: false 7 | default: '11' 8 | shell: 9 | description: 'shell must be specified for composite actions - so we setup an appropriate default' 10 | required: false 11 | default: ${{ contains(runner.os, 'windows') && 'pwsh' || 'bash' }} 12 | clj-cache-prefix: 13 | description: 'cache key prefix' 14 | required: false 15 | default: clj-deps 16 | clj-cache-hash-files: 17 | description: 'base cache hash on these files' 18 | required: false 19 | default: "'deps.edn','bb.edn'" 20 | 21 | runs: 22 | using: 'composite' 23 | 24 | steps: 25 | - name: Clojure deps cache 26 | uses: actions/cache@v4 27 | with: 28 | path: | 29 | ~/.m2/repository 30 | ~/.deps.clj 31 | ~/.gitlibs 32 | key: ${{ runner.os }}-${{ inputs.clj-cache-prefix }}-${{ hashFiles(inputs.clj-cache-hash-files) }} 33 | restore-keys: ${{ runner.os }}-${{ inputs.clj-cache-prefix }} 34 | 35 | - name: Setup Java 36 | uses: actions/setup-java@v4 37 | with: 38 | distribution: 'temurin' 39 | java-version: ${{ inputs.jdk }} 40 | if: inputs.jdk != 'skip' 41 | 42 | - name: Install Babashka & Clojure 43 | uses: DeLaGuardo/setup-clojure@13.2 44 | with: 45 | bb: 'latest' 46 | cli: 'latest' 47 | 48 | - name: Tools Versions 49 | shell: ${{ inputs.shell }} 50 | run: | 51 | echo "java -version" 52 | java -version 53 | echo "bb --version" 54 | bb --version 55 | echo "clojure --version" 56 | clojure --version 57 | 58 | - name: Download Clojure Dependencies 59 | shell: ${{ inputs.shell }} 60 | run: bb download-deps 61 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | # allow this workflow to be called from other workflows, namely: publish 5 | workflow_call: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | setup: 15 | runs-on: ubuntu-latest 16 | 17 | outputs: 18 | tests: ${{ steps.set-tests.outputs.tests }} 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup 25 | uses: ./.github/workflows/shared-setup 26 | 27 | - id: set-tests 28 | name: Set test var for matrix 29 | # run test.clj directly instead of via bb task to avoid generic task output 30 | run: echo "tests=$(bb script/ci_unit_tests.clj matrix-for-ci --format=json)" >> $GITHUB_OUTPUT 31 | 32 | build: 33 | needs: setup 34 | runs-on: ${{ matrix.os }}-latest 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | include: ${{ fromJSON(needs.setup.outputs.tests) }} 39 | 40 | name: ${{ matrix.desc }} 41 | 42 | steps: 43 | # 44 | # Tell git not to convert newlines on checkout for Windows 45 | # 46 | - name: Prepare git (Windows) 47 | run: git config --global core.autocrlf false 48 | if: matrix.os == 'windows' 49 | 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | 53 | - name: Setup 54 | uses: ./.github/workflows/shared-setup 55 | with: 56 | jdk: ${{ matrix.jdk }} 57 | 58 | - name: Install Planck 59 | uses: ./.github/workflows/setup-planck 60 | if: contains( matrix.requires, 'planck' ) 61 | 62 | - name: Node modules cache 63 | uses: actions/cache@v4 64 | with: 65 | path: ./node_modules 66 | key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} 67 | restore-keys: ${{ runner.os }}-node- 68 | if: contains( matrix.requires, 'npm' ) 69 | 70 | - name: Install node packages 71 | run: npm ci 72 | if: contains( matrix.requires, 'npm' ) 73 | 74 | - name: Run Tests 75 | run: ${{ matrix.cmd }} 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.calva 2 | /.clj-kondo/.cache 3 | /.clj-kondo/rewrite-clj 4 | /.cpcache 5 | /.diff-apis 6 | /.eastwood 7 | /.lsp 8 | .nrepl-port 9 | /.shadow-cljs 10 | /.vscode 11 | /classes 12 | /log 13 | /node_modules 14 | /target 15 | /fiddle.clj 16 | /faddle.clj 17 | /fiddle/ 18 | /.cljdoc-preview 19 | /.cljs_node_repl 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | **Do** 4 | - remember that a gift, while appreciated, is also a burden. We value your input but start with an issue to propose your change before investing your valuable time in a PR. 5 | - read the [rewrite-clj Developer Guide](doc/02-developer-guide.adoc). 6 | - follow [the seven rules of a great Git commit message][1]. 7 | - follow [the Clojure Style Guide][2]. 8 | - include/update tests for your change. 9 | - ensure that the Continuous Integration checks pass. 10 | - feel free to pester the project maintainers about your PR if it hasn't been responded to. Sometimes notifications can be missed. 11 | 12 | **Don't** 13 | - include more than one feature or fix in a single PR. 14 | - include changes unrelated to the purpose of the PR. This includes changing the project version number, adding lines to the 15 | `.gitignore` file, or changing the indentation or formatting. 16 | - open a new PR if changes are requested. Just push to the same branch and the PR will be updated. 17 | - overuse vertical whitespace; avoid multiple sequential blank lines. 18 | 19 | [1]: https://chris.beams.io/posts/git-commit/#seven-rules 20 | [2]: https://github.com/bbatsov/clojure-style-guide 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2018 Yannick Scherer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ORIGINATOR: -------------------------------------------------------------------------------- 1 | @xsc 2 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:min-bb-version "0.9.162" 2 | :paths ["script" "build"] 3 | :deps {org.clojure/data.zip {:mvn/version "1.1.0"} 4 | io.aviso/pretty {:mvn/version "1.4.4"} 5 | dev.nubank/docopt {:mvn/version "0.6.1-fix7"} 6 | doric/doric {:mvn/version "0.9.0"} 7 | version-clj/version-clj {:mvn/version "2.0.3"} 8 | lread/status-line {:git/url "https://github.com/lread/status-line.git" 9 | :sha "cf44c15f30ea3867227fa61ceb823e5e942c707f"} 10 | etaoin/etaoin {:mvn/version "1.1.43"} 11 | io.github.babashka/neil {:git/tag "v0.3.68", :git/sha "78ffab1"} } 12 | :tasks {;; setup 13 | :requires ([clojure.string :as string] 14 | [lread.status-line :as status]) 15 | :enter (let [{:keys [name]} (current-task)] (status/line :head "TASK %s %s" name (string/join " " *command-line-args*))) 16 | :leave (let [{:keys [name]} (current-task)] (status/line :detail "\nTASK %s done." name)) 17 | ;; commands 18 | download-deps {:task download-deps/-main :doc "bring down Clojure deps"} 19 | apply-import-vars {:task apply-import-vars/-main :doc "(check|gen-code) - export APIs statically from templates"} 20 | dev-jvm {:task dev-repl/dev-jvm :doc "launch jvm nREPL for development, --help for usage"} 21 | dev-cljs {:task dev-repl/dev-cljs :doc "launch cljs nREPL for development, --help for usage"} 22 | lint {:task lint/-main :doc "[--rebuild] lint source code using clj-kondo, eastwood"} 23 | -lint-kondo {:task lint-kondo/-main :doc "[--rebuild]"} 24 | -lint-eastwood {:task lint-eastwood/-main} 25 | test-clj {:task test-clj/-main :doc "[--clojure-version (1.8|1.9|1.10|1.11|1.12)]"} 26 | test-cljs {:task test-cljs/-main :doc "use --help for args"} 27 | test-shadow-cljs {:task test-shadow-cljs/-main} 28 | test-native {:task test-native/-main :doc "run rewrite-clj and tests after both compiled with GraalVM native-image"} 29 | test-native-sci {:task test-native-sci/-main :doc "interpret rewrite-clj tests via sci from rewrite-clj native image"} 30 | test-jvm-sci {:task test-jvm-sci/-main :doc "JVM only sanity test for test-native-sci"} 31 | test-clj-watch {:task test-clj-watch/-main :doc "[kaocha args]"} 32 | test-cljs-watch {:task test-cljs-watch/-main :doc "watch cljs test with fighweel main"} 33 | test-coverage {:task test-coverage/-main :doc "generate code coverage reports for Clojure tests"} 34 | test-doc {:task test-doc/-main :doc "test doc code blocks [--platform (clj|cljs|all)]"} 35 | test-libs {:task test-libs/-main :doc "(list|run|outdated) - verify that libs using rewrite-clj* work with current rewrite-clj"} 36 | outdated {:task outdated/-main :doc "report on outdated Clojure and npm dependencies"} 37 | doc-update-readme {:task doc-update-readme/-main :doc "honour our contributors in README"} 38 | cljdoc-preview {:task cljdoc-preview/-main :doc "preview what docs will look like on cljdoc, use --help for args"} 39 | ci-unit-tests {:task ci-unit-tests/-main :doc "run/list continuous integration unit tests, use --help for args"} 40 | pubcheck {:task publish/pubcheck :doc "Run only publish checks (without publishing)"} 41 | publish {:task publish/-main :doc "Publish a release (for maintainers)"} 42 | ;; let's not rely on a random version of neil 43 | neil {:task babashka.neil/-main :doc "Pinned version of babashka/neil (used in scripting)"} 44 | 45 | ;; hidden tasks, no need for folks to be trying these ci invoked tasks 46 | -ci-clojars-deploy {:task ci-publish/clojars-deploy :doc "triggered on ci by release tag" } 47 | -ci-github-create-release {:task ci-publish/github-create-release :doc "triggered on ci by release tag" } 48 | -ci-cljdoc-request-build {:task ci-publish/cljdoc-request-build :doc "ask cljdoc to build docs for new release" }}} 49 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [build-shared] 3 | [clojure.edn :as edn] 4 | [clojure.tools.build.api :as b])) 5 | 6 | (def version (build-shared/lib-version)) 7 | (def lib (build-shared/lib-artifact-name)) 8 | 9 | (def class-dir "target/classes") 10 | (def basis (b/create-basis {:project "deps.edn"})) 11 | (def jar-file (format "target/%s.jar" (name lib))) 12 | 13 | (defn jar 14 | "Build library jar file. 15 | Supports `:version-override` for local testing, otherwise official version is used. 16 | For example, when testing 3rd party libs against rewrite-clj HEAD we use the suffix: canary." 17 | [{:keys [version-override] :as opts}] 18 | (b/delete {:path class-dir}) 19 | (b/delete {:path jar-file}) 20 | (let [version (or version-override version)] 21 | (println "jarring version" version) 22 | (b/write-pom {:class-dir class-dir 23 | :lib lib 24 | :version version 25 | :basis basis 26 | :src-dirs ["src"] 27 | :scm {:url "https://github.com/clj-commons/rewrite-clj" 28 | :connection "scm:git:git@github.com:clj-commons/rewrite-clj.git" 29 | :developerConnection "scm:git:git@github.com:clj-commons/rewrite-clj.git" 30 | :tag (format "v%s" version)} 31 | :pom-data [[:description "Rewrite Clojure code and edn"] 32 | [:url "https://github.com/clj-commons/rewrite-clj"] 33 | [:licenses 34 | [:license 35 | [:name "The MIT License"] 36 | [:url "http://opensource.org/licenses/MIT"]]] 37 | [:properties 38 | [:project.build.sourceEncoding "UTF-8"]]]}) 39 | (b/copy-dir {:src-dirs ["src" "resources"] 40 | :target-dir class-dir}) 41 | (b/jar {:class-dir class-dir 42 | :jar-file jar-file}) 43 | (assoc opts :built-version version))) 44 | 45 | (defn install 46 | "Install built jar to local maven repo, optionally specify `:version-override` for local testing." 47 | [opts] 48 | (let [{:keys [built-version]} (jar opts)] 49 | (println "installing version" built-version) 50 | (b/install {:class-dir class-dir 51 | :lib lib 52 | :version built-version 53 | :basis basis 54 | :jar-file jar-file}))) 55 | 56 | (defn deploy [_] 57 | (jar {}) 58 | (println "deploy") 59 | ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 60 | {:installer :remote 61 | :artifact jar-file 62 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})})) 63 | 64 | (defn download-deps 65 | "Download all deps for all aliases" 66 | [_] 67 | (let [aliases (->> "deps.edn" 68 | slurp 69 | edn/read-string 70 | :aliases 71 | keys)] 72 | ;; one at a time because aliases with :replace-deps will... well... you know. 73 | (println "Bring down default deps") 74 | (b/create-basis {}) 75 | (doseq [a (sort aliases)] 76 | (println "Bring down deps for alias" a) 77 | (b/create-basis {:aliases [a]})))) 78 | -------------------------------------------------------------------------------- /build/build_shared.clj: -------------------------------------------------------------------------------- 1 | (ns build-shared 2 | "a few things that are both needed by bb script code and build.clj" 3 | (:require [clojure.string :as string] 4 | [clojure.edn :as edn])) 5 | 6 | (defn- project-info [] 7 | (-> (edn/read-string (slurp "deps.edn")) 8 | :aliases :neil :project)) 9 | 10 | (def version-tag-prefix "v") 11 | 12 | (defn lib-version [] 13 | (-> (project-info) :version)) 14 | 15 | (defn lib-artifact-name [] 16 | (-> (project-info) :name)) 17 | 18 | (defn lib-github-coords [] 19 | (-> (project-info) :github-coords)) 20 | 21 | (defn version->tag [version] 22 | (str version-tag-prefix version)) 23 | 24 | (defn tag->version [ci-tag] 25 | (and (string/starts-with? ci-tag version-tag-prefix) 26 | (string/replace-first ci-tag version-tag-prefix ""))) 27 | -------------------------------------------------------------------------------- /doc/03-faq.adoc: -------------------------------------------------------------------------------- 1 | = Frequently Asked Questions 2 | 3 | == Documentation 4 | 5 | === What is the meaning of the ^:no-doc metadata? 6 | Our goal is to produce documentation for users of rewrite-clj. 7 | As such we only want to document public APIs. 8 | 9 | `^:no-doc` is a signal to https://cljdoc.org/[cljdoc] that source code should not be included in generated documentation. 10 | This metadata convention was introduced by https://github.com/weavejester/codox[codox]. 11 | 12 | == What the markdown? 13 | Stand alone articles are written up in https://asciidoctor.org/docs/what-is-asciidoc/[AsciiDoc]. 14 | 15 | 1. it is a richer markup language than GitHub markdown. 16 | 2. is supported by cljdoc. 17 | 18 | Our docstrings sometimes take advantage of https://commonmark.org/[CommonMark] which is supported by cljdoc for docstrings. 19 | GitHub uses CommonMark as part of of its markdown solution. 20 | -------------------------------------------------------------------------------- /doc/04-maintainer-guide.adoc: -------------------------------------------------------------------------------- 1 | = Maintainer Guide 2 | :toclevels: 5 3 | :toc: 4 | 5 | == Audience 6 | You are a maintainer of this project. 7 | 8 | == Publishing a New Release 9 | Is invoked from the command line via: 10 | 11 | [source,shell] 12 | ---- 13 | bb publish 14 | ---- 15 | 16 | The publish task locally validates: 17 | 18 | * local git 19 | ** you are not on a fork 20 | ** you are on the default branch 21 | ** do not have any uncommitted code 22 | ** do not have any unpushed commits 23 | ** local head sha matches matches remote head sha 24 | * changelog 25 | ** Has an "Unreleased" section with content 26 | 27 | TIP: to run these validations without publishing, run `bb pubcheck` 28 | 29 | Then also locally: 30 | 31 | . bumps the version `` (our scheme is `major.minor.`) 32 | ** Our version is stored in `deps.edn` under `:aliases` `:neil` `:project` `:version` 33 | . applies version to: 34 | .. `doc/01-user-guide.adoc` 35 | .. `CHANGELOG.adoc` 36 | . git commits: `deps.edn` `doc/01-user-guide.adoc` `CHANGELOG.adoc` 37 | . git tags with release tag `v` 38 | . pushes commit 39 | . pushes tag 40 | 41 | Then up on CI, the CI publish workflow is only triggered when it sees a release tag: 42 | 43 | . CI tests workflow is invoked 44 | . a release jar is published to clojars 45 | . a GitHub release is created 46 | . cljdoc is informed of the new release 47 | 48 | == Relevant Sources 49 | 50 | Scripts: 51 | 52 | . `bb.edn` - tasks entry point 53 | . `script/publish.clj` - client side work 54 | . `script/ci_publish.clj` - ci side work 55 | 56 | CI - We use GitHub Actions for this project 57 | 58 | . `.github/workflows/publish.yml` 59 | 60 | == CI Config 61 | 62 | Clojars secrets are protected under the `publish` environment which is only referenced by `publish.yml`. 63 | 64 | Codecov.io token is configured as a `CODECOV_TOKEN` secret in this repo. 65 | Token was generated and populated by clj-commons GitHub org admins who have the privs to do such things. 66 | Hit them up in Clojurians Slack `#clj-commons` channel if you should ever need their help. 67 | 68 | == Expected Oddities 69 | 70 | When publishing, you will see both the `tests` workflow triggered and the `publish` workflow triggered (which also invokes the `tests` workflow). 71 | 72 | This extra running of the `tests` workflow is GitHub Actions responding to changes committed as part of the publishing work. 73 | Annoying, but harmless. 74 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree 2 | [["Readme" {:file "README.adoc"}] 3 | ["Changelog" {:file "CHANGELOG.adoc"}] 4 | ["User Guide" {:file "doc/01-user-guide.adoc"}] 5 | ["Developer Guide" {:file "doc/02-developer-guide.adoc"}] 6 | ["Design" {} 7 | ["Merging rewrite-clj and rewrite-cljs" {:file "doc/design/01-merging-rewrite-clj-and-rewrite-cljs.adoc"} 8 | ["Namespaced Elements" {:file "doc/design/namespaced-elements.adoc"}]]] 9 | ["Frequently Asked Questions" {:file "doc/03-faq.adoc"}] 10 | ["Contributing" {:file "CONTRIBUTING.md"}] 11 | ["Code of Conduct" {:file "CODE_OF_CONDUCT.md"}] 12 | ["Maintainer Guide" {:file "doc/04-maintainer-guide.adoc"}]]} 13 | -------------------------------------------------------------------------------- /doc/diff-notes/rewrite-clj-v0-lang-clj-and-rewrite-clj-v1-lang-clj.adoc: -------------------------------------------------------------------------------- 1 | The api of the last released version of rewrite-clj v0 was used as a reference for rewrite-clj v1. 2 | As such, you'll notice the apis are almost the same. 3 | 4 | I assume that `rewrite-clj.custom-zipper.core` is internal and marked it as such with `:no-doc`. 5 | 6 | There were some features unique to rewrite-cljs (such as paredit and some positional searching) which were brought over to rewrite-clj v1. 7 | 8 | The internal rewrite-cljs namespaces that were link:../../design/01-merging-rewrite-clj-and-rewrite-cljs.adoc#_clojurescript_namespace_clashes[renamed to avoid cljs namespace collisions] also occur in the clj side of rewrite-clj v1. 9 | All other differences are considered internal refactorings. 10 | -------------------------------------------------------------------------------- /doc/diff-notes/rewrite-clj-v0-lang-clj-and-rewrite-cljs-lang-cljs.adoc: -------------------------------------------------------------------------------- 1 | The apis of the last released version of rewrite-cljs and the last released version of rewrite-clj v0 are compared here. 2 | 3 | In short, rewrite-cljs lagged far behind rewrite-clj v0, but rewrite-cljs had added some features of its own. 4 | 5 | See link:../../design/01-merging-rewrite-clj-and-rewrite-cljs.adoc[rewrite-clj v1's design docs] for more details. 6 | -------------------------------------------------------------------------------- /doc/diff-notes/rewrite-clj-v1-lang-cljs-and-rewrite-clj-v1-lang-clj-documented-only.adoc: -------------------------------------------------------------------------------- 1 | When we exclude API elements that are considered internal, we are left with the expected feature differences between cljs and clj usages, namely: file support. 2 | -------------------------------------------------------------------------------- /doc/diff-notes/rewrite-clj-v1-lang-cljs-and-rewrite-clj-v1-lang-clj.adoc: -------------------------------------------------------------------------------- 1 | The cljs and clj sides of rewrite-clj v1 have the following differences of note: 2 | 3 | 1. You'll notice that the Clojure API has the ability to deal with files, the ClojureScript API does not. 4 | 2. If we were to exclude api namespaces and functions marked with `no-doc` we link:rewrite-clj-v1-lang-cljs-and-rewrite-clj-v1-lang-clj-documented-only.adoc[would see only item 1 as differences]. 5 | We include them because it seems that historically, internal undocumented features have been used in rewrite-cljs and rewrite-clj. 6 | 3. The ClojureScript API is missing the Clojure API namespaces that cause link:../../design/01-merging-rewrite-clj-and-rewrite-cljs.adoc#_clojurescript_namespace_clashes[namespace clashes on the clojurescript side]. 7 | -------------------------------------------------------------------------------- /doc/diff-notes/rewrite-cljs-lang-cljs-and-rewrite-clj-v1-lang-cljs.adoc: -------------------------------------------------------------------------------- 1 | You'll notice that the API of the last released version of rewrite-cljs as compared to the cljs api of rewrite-clj v1 has many differences. 2 | 3 | Rewrite-cljs lagged far behind rewrite-clj v0. 4 | The cljs side of rewrite-clj v1 adds in, where possible, rewrite-clj features. 5 | -------------------------------------------------------------------------------- /doc/generated/contributors/AndreaCrotti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/AndreaCrotti.png -------------------------------------------------------------------------------- /doc/generated/contributors/FiV0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/FiV0.png -------------------------------------------------------------------------------- /doc/generated/contributors/NoahTheDuke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/NoahTheDuke.png -------------------------------------------------------------------------------- /doc/generated/contributors/PEZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/PEZ.png -------------------------------------------------------------------------------- /doc/generated/contributors/RickMoynihan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/RickMoynihan.png -------------------------------------------------------------------------------- /doc/generated/contributors/SevereOverfl0w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/SevereOverfl0w.png -------------------------------------------------------------------------------- /doc/generated/contributors/anmonteiro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/anmonteiro.png -------------------------------------------------------------------------------- /doc/generated/contributors/arrdem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/arrdem.png -------------------------------------------------------------------------------- /doc/generated/contributors/awb99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/awb99.png -------------------------------------------------------------------------------- /doc/generated/contributors/bbatsov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/bbatsov.png -------------------------------------------------------------------------------- /doc/generated/contributors/bobbicodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/bobbicodes.png -------------------------------------------------------------------------------- /doc/generated/contributors/borkdude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/borkdude.png -------------------------------------------------------------------------------- /doc/generated/contributors/brian-dawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/brian-dawn.png -------------------------------------------------------------------------------- /doc/generated/contributors/danielcompton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/danielcompton.png -------------------------------------------------------------------------------- /doc/generated/contributors/doby162.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/doby162.png -------------------------------------------------------------------------------- /doc/generated/contributors/drorbemet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/drorbemet.png -------------------------------------------------------------------------------- /doc/generated/contributors/eraserhd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/eraserhd.png -------------------------------------------------------------------------------- /doc/generated/contributors/ericdallo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/ericdallo.png -------------------------------------------------------------------------------- /doc/generated/contributors/expez.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/expez.png -------------------------------------------------------------------------------- /doc/generated/contributors/fazzone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/fazzone.png -------------------------------------------------------------------------------- /doc/generated/contributors/ferdinand-beyer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/ferdinand-beyer.png -------------------------------------------------------------------------------- /doc/generated/contributors/frenchy64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/frenchy64.png -------------------------------------------------------------------------------- /doc/generated/contributors/green-coder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/green-coder.png -------------------------------------------------------------------------------- /doc/generated/contributors/guoyongxin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/guoyongxin.png -------------------------------------------------------------------------------- /doc/generated/contributors/ikappaki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/ikappaki.png -------------------------------------------------------------------------------- /doc/generated/contributors/immoh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/immoh.png -------------------------------------------------------------------------------- /doc/generated/contributors/ivarref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/ivarref.png -------------------------------------------------------------------------------- /doc/generated/contributors/jespera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/jespera.png -------------------------------------------------------------------------------- /doc/generated/contributors/kkinnear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/kkinnear.png -------------------------------------------------------------------------------- /doc/generated/contributors/lread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/lread.png -------------------------------------------------------------------------------- /doc/generated/contributors/luxbock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/luxbock.png -------------------------------------------------------------------------------- /doc/generated/contributors/mainej.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/mainej.png -------------------------------------------------------------------------------- /doc/generated/contributors/martinklepsch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/martinklepsch.png -------------------------------------------------------------------------------- /doc/generated/contributors/matanox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/matanox.png -------------------------------------------------------------------------------- /doc/generated/contributors/mhuebert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/mhuebert.png -------------------------------------------------------------------------------- /doc/generated/contributors/mikekap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/mikekap.png -------------------------------------------------------------------------------- /doc/generated/contributors/mjayprateek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/mjayprateek.png -------------------------------------------------------------------------------- /doc/generated/contributors/mrkam2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/mrkam2.png -------------------------------------------------------------------------------- /doc/generated/contributors/msgodf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/msgodf.png -------------------------------------------------------------------------------- /doc/generated/contributors/mynomoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/mynomoto.png -------------------------------------------------------------------------------- /doc/generated/contributors/optevo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/optevo.png -------------------------------------------------------------------------------- /doc/generated/contributors/p4ulcristian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/p4ulcristian.png -------------------------------------------------------------------------------- /doc/generated/contributors/plexus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/plexus.png -------------------------------------------------------------------------------- /doc/generated/contributors/rfhayashi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/rfhayashi.png -------------------------------------------------------------------------------- /doc/generated/contributors/rgkirch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/rgkirch.png -------------------------------------------------------------------------------- /doc/generated/contributors/rundis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/rundis.png -------------------------------------------------------------------------------- /doc/generated/contributors/shaunlebron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/shaunlebron.png -------------------------------------------------------------------------------- /doc/generated/contributors/shaunxcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/shaunxcode.png -------------------------------------------------------------------------------- /doc/generated/contributors/shmish111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/shmish111.png -------------------------------------------------------------------------------- /doc/generated/contributors/slipset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/slipset.png -------------------------------------------------------------------------------- /doc/generated/contributors/snoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/snoe.png -------------------------------------------------------------------------------- /doc/generated/contributors/sogaiu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/sogaiu.png -------------------------------------------------------------------------------- /doc/generated/contributors/stathissideris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/stathissideris.png -------------------------------------------------------------------------------- /doc/generated/contributors/swannodette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/swannodette.png -------------------------------------------------------------------------------- /doc/generated/contributors/theronic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/theronic.png -------------------------------------------------------------------------------- /doc/generated/contributors/vemv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/vemv.png -------------------------------------------------------------------------------- /doc/generated/contributors/weavejester.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/weavejester.png -------------------------------------------------------------------------------- /doc/generated/contributors/xsc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/xsc.png -------------------------------------------------------------------------------- /doc/generated/contributors/zcaudate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/generated/contributors/zcaudate.png -------------------------------------------------------------------------------- /doc/introduction-parsed-nodes.drawio: -------------------------------------------------------------------------------- 1 | 7V1rs5s2E/41Z6btjBl0A/Ex56RJpm06zeSdpP3U4djYJsHmBONz6a9/hQ02oMWADYLEIpnEyLKMdx/tTavVDblbPb+N3Ifl+3DmBTfYnD3fkNc3GCPuYPFf0vKSttjc2bcsIn+2bzOPDR/9/7y0Y9a69WfeJm3bN8VhGMT+Q7FxGq7X3jQutLlRFD4Vu83DYFZoeHAXXuExkoaPUzfwpG6f/Vm83Ldyluv9zvMXy+ybkZm+s3KzzmnDZunOwqdcE/n1htxFYRjvX62e77wgoV6RLm8q3j08WOSt4yYfsKMV+er+drd42v759CF6/OZ9eDfBiOzHeXSDbfqT08eNXzIaeOvZq4SU4m4auJuNP70ht8t4FYgGJF7Ow3X8xl35QcLku3Ab+V4kRvjTe0rfTNmKxG++3cRR+NW7C4Mw2o1OrN0l3vGe/fhv0WYaLL37J/2C5PXrBFNmdvOS3cwEt9InDaN4GS7CtRv8emy9nW6jR2+WPakfBLmvnrPkj2iXaZmSdyN+zdQ7SUBr3zN5ktxHUx689cKVF0cvosPTET4ZepY55GRtkRe4sf9YhJ+bonhxGO7wDX+FvnhmbKZTbkIdxyDYOV77EV6yt22zOOT+F6aj5OFTHhhRqzgUsZziULEbLbxYGkpgx33JdXtIOmxO/YRs8pe/KR1QvNiPmd3l6Hxs2sG/1VSw6qdCFG7XswOcnpZ+7H18cHcIeRISsDgvpuEqmSo7oLaaI27gL9biJvDmAh23i2TSpeNUT59KDD96Uew9n8RmRnnm0CLlWSauc+glFjcsLiMYoRIe8mjN8a49axCmMmvY7QnumO24I1HVsWziWrLE8NCMebbMojwHaR27O+AUcQxkF1mFslmTY5Vjy2yyaF9cQrjNBPrRWTRBBBu0JH4xohKTOKANemNSZo1cou8FTaKXnKZObv/JFHJyc9TVu7uDsj5DwbfiWpV6P0No1ir+TPntld6pjnhUBoKVfeQgN3gJa00tAsuqG6nCIOhKYzsNsHxF8gabyCCkXt7Y5qGbEpHjIIBNVpDQaOY/FthlfduGGe0mmx3xXokOiD48H98Urxbp/7tB7qNyi3jM3chZ6yVGXMIAX/iDr1LuxuHDhbwuS6m5Z02nEOJmtnNvml1pI8eS0WHK1p0FiR7WFzQgi2HPw4SEicPuTovQ+ORFM3ft5uHQAEAYAtCjN43DKAea/XdWoEYQOi5Co8iwdbj2StxNm0pgKSNq5c9mO1cVQmJRfp3lUSSA3IhR/fXifwl4XzudQcosupq8LHyIBC9KDQswSrHZF8IaxDeuR0dgh0t+A6wiVOoHwLnT+kGxfiAmkZEBqAcGOP79qQc2pHoI/E2slcO5eEIAngB9QLBCZYDAaLcWNS0c5mS4XPv+6sqewLYcMAFkkI0UyiCEBhVCa+8p8IWg0HLoXFAhB4jCAYapY1iWUlkEucUldi4EZR+aU+GwDOreZyOYNdQhFKCOHEhmgM3O+iNNtUWYTrn62cSKs0lQJhNax8ZMrFXL80MDu1vn5uA9IOZbzstsvk0FV8WcODHjIk/8rJSjyZxK18/EuEw81+tkrG0cbtKJdJxHafeVGy38dcXcPFdTvNldJzWFDNuaedB4TiMmfEhWAC1mEmQxYKmeCpdeuEKlLYvxWhaUyqtlpowYDsj//uwKbA9pV+wAsdkBQpsW56GKIQBVtowqqtKqYCrzeRLK9pO10158nLsoWQmE2hXALG5VuwKIaMqUkSwBUlQK1LJS4kbTFUBklxJ1Ds6SohVA0sCGvp7oLjUbBXcR5Of0l3AAZVVpw2gkhhFrFPRFoPvXl2VUbxgp9/72f8Vv3Q21WiUU1x7h5R5he5sLsOQ5IOIIFMrpy/nLAijDWPIZHrUZfyakcKOFikN6pRI7nmvDJh+mpMQscwia9CqzXTnWds0JdSCAw2cUAhvH96SzhHWKWQkYkPmCVQZ2eHUksFvzJSVkvfmyepnMt+tp7IfaYmlksXB82yFEy9424nLoEWWwVWKw8Nr1lT4NllgQe63Nlc7wNLy1ggjX5spNPhObGKi0W+6wOyafyEDAZMj+duiQak9FGy3KjBaUZGGW4QGsHFgwPPpbkqLVmfpaL4xdLyAHAUJHTuVQnYFNqx2lxvYw7dQedrUVrNwKTvYKIJbbK1DaaJy503kbRmkCB4JU4/WaMMQ0eGm3ECRMlG4UQODmVW2+KDZfMJOhAWTTMMCj7TGbptZ0UR11+eX70TLSis2Ue/dzxfqnGy0jQZPLQgupjLWgTI9oo/o7NKoxAmSd7KkxrNCgPqQTaVNl9y6xDGSbxwvXmy1qoy5Ub3Ac3mxBFJ2GCWTCqI2+qNxotMPKVeoK3JOuQLRGDplcAhhVGYg5FHDTkZgrjsQgyk/CFLKXlQZiLCj9r8T6/vfF0RJZqAlYESpLpVh66abzKgk1AKyfUwUwGGb+gvYomwZF1vGye4KKXR0mU+BxzsNotfnhrYiGHufFCMvKKRgMOxQh26K2yTjPZGK+kgI3OCbHbgAAETdI4bJ6QiBrVUr1R/dQJ8xpkL5oAVGp3nxSVp1jr7WJskg645ZhlrN5hq7JxqBcnkEj6TNvrhMXC7ZOcvXtKAggmIbFj8qitGILpTFSldsuWLVFrAPrYw+sM44NKXcbSGRk9NBNTZDEhhYSr9d0wU6DirIOUpzHaGNtvox2U+kEc8fIMk1PmTWcGKbSkhs2uDXDXSXkX99vHro2HtoL5XHvvhScxQYQwchtxTSgvQ0GL3idSgti2IPuddBlVroAHREAqgQdZDU4tqHSaABX7kp8bVJ05eK6KmfWQOlYt1Qyvb6kSnbEV21JlWzHyUhKqpAkspaL/pZ9pXKtvKYVVhzSbtye663gBvt8Wpwegq/4+BDSFOnZvqGxIN0uK31WWsdoim2agadypJ7RfIhg5NGbK02V6tWibjzWq/oj3Km5BIBfvDh+SXGWxIMqEI8KcD/K6LPEcgnZ50PxULSpARZZQyw2Btllxh3nmoG5zKEGlcjGxkC96yJ/PINJW6cyqj3iDTxySQdexhF4wabTOsVRbc3TYc/70c745RBDdUmOQxdA1Qf+FI91AYrUDp4Qr0/8GUH2ATGtRlWx1SbBKz3yRyfBd3/sD1QUe+jEd2d0ie9E57OoTnyHTqQaPNk9896uPIDhNA5gOOMKYGSHO2trcy/qaSmlhwBVr8Dy47gvBulzWYY/XFLeTkOgoyVVFoyw9NGS32sMYoKIbRDmmA5P4lcWoVQCl+FgzmwBu92/csYaNQ2bIWpbSAzmUAfMFjDyY2DeFxTBDJXr1SEs+8zpjENAh/QVr7CrZYXWIUMHvScM2U2zDYlKDQOfG6qTDVsw1jydbEjGlmxoV++q0ssb34VpwVCS4MoPV1muALmGlmPYUMygv2iWLrZctBeAE2xHsEfB0Rv2R2w1UG5wO1eOdITbFbBZvUym1cl3oU5okr5br0IUp6tjs0G++vVoEGyezmkYgzbBZvUSltYmQ2uTJPHK5jkElef8KLRJ7b5+rU1GrU2Sc+9PbQYZgWY5HC17vpjC9ikx1XS5vtEo4BJ+uu4hffanStzWj1mVFlAqbFEc1rxkZPjIrzO+oIocSbpA9wRxLxiz+klf14xa3VxQgGOCcSmM2IiVlcfdEuB0W00xxbLgl5QB5IKxq573559Hx8/W9mZ1aLxOzbdOeWpliB7zo3A3an5XFSrTo5luZ0wOdRMGxLbtDkq6vZvdf7tdflp/Waw+//v5E//jeftmgiHFrlczLjhlGEo+U7R6AXJYF8e40Dwv8deWnb5B+QtGEzV/Wxy8KYll8Oz5IXms15wvPvZq9DwGXWzN47PlNOLA+blDMhjMItAMbsxgbBsc55b2pPO3h+P3in1aIG/69fd/fwv++vD3l/fryd2kQcVBMYr/sKniVmWMVKYsUBtfIvYJFWhSaUGLEiALJ+uUp18XCZQg/aqXSYVztq72VecpbF/twwH7GZYsDdD9dElj38X2pO/u33LYm0DurqDtxtuIt+Mw5+fuH0t2iAd80NFQLF56ZWk3D4MgfEqcbEkMapI2eJB1OPN2DxBtp/E28irJ1vkKTBfKpwu5RbgNyC3I/4bOM+VGe8klbqMw4cLhvbfixy3fC1YkPf4P -------------------------------------------------------------------------------- /doc/introduction-parsed-nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/introduction-parsed-nodes.png -------------------------------------------------------------------------------- /doc/map-nodes.drawio: -------------------------------------------------------------------------------- 1 | 7V1bc+I4Fv41POKyJEuyHzvp7e6aS9dM9dbO9qODFfC0wbRxLuyvXwnk4MuxIQm2zCBSleCDUeCcz/qOzkWekNvl8+csXC9+TyORTLAbPU/IxwnGyA+w/KMkWy0hfrCXzLM42svcg+Bb/D+hTyykD3EkNlq2F+VpmuTxuiqcpauVmOUVWZhl6VP1tPs0iSqCdTgXlY+hBN9mYSIap/0VR/liL/Vp6ewvIp4viv+MXP3KMixO1oLNIozSp5KI/GtCbrM0zffPls+3IlHaq+rlU8urLx8sE6v8lDfwbEl+hL/czp8evj79mT3+FH9+mfp6mMcwedDfWH/afFuoIEsfVpFQo7gTcvO0iHPxbR3O1KtP0upStsiXiTxC8uksXcYzfeomz9If4jZN0mw3EgkYJyGTr9zHSVKSCxRRwaU8TOL5SsoScZ+r09JVriGBPH38KVzGiULTbfqQxSKTn/WrkGq9aeqj+G4iy8VzSaT181mkS5FnW3mKfnXqUeYwb/82jdgpxhqxTwf7M7oXLUqmL94XasTNX4Y/GEU+0XZ5jY0wYCOWKAVF8WPFVuznQ1oobrrZae6DPAF56+fDi/LZXP/dDXKX1SXyY+5GLqStgEDHAaG0H8vr6YM2bZ6u32noGngkdPzIg+Dm4zvC2NmggQBoeA1oUNaEBqV9QYO0QkOp8CRs0Co25ExaKPAgLFTZjqFCgEoYugNwtf9YLcCStsir6ClwMpOWkxBowmkZR5F6+00m5HcK73ZDqclnncarfKdvejOhH9VYD3m60SBTcJGQjVdzffoyzObxSh+UJ7H3QPPT7gFD8+aM0PS8JjQLHipBEwVNaL5w5dmh6R2D5v1+yjiA6j8ii8JVWEHjcfxiaG7LpbpXb4dd1V6rdCVqxtWi2jTWCk5ojqzSah1y7RCr4VHD+N9qWv0Y9Iko7DcQ5bkAotyeEFXMrNZX2dmDIXqarwJc9b35KpRbX8W8r8IQcjg+7qsw4PLtzVeh/th8FTloaN2VAwbvd4/e3RXmqomLvDxO8FwK6hnEc6GBSc/lh9g+pVlkfZe3w4s7RZCpy3ehnoOBZXxv7guG5r+aNcUq+qCCVmoKScLNRqm1bFuplmz7X6VqhxaH37XmdwcfnytH2+LoOc5Lb5NH34sR5fPDm9TBdvKGmaQxY7Ddo8ukGzmEvoxghTG9uM3lXCfyEy5YERWxvBaIlK0PUZ+WZSIJ8/ixGgGE8KD/wx9q7j4gkHBeRR8qvKJijP1X128rh+xqI3kFRFpH2uumMdIOoC9f/O2YZVDo6Ypdbuo6QflRtQ4htOliAeGgQqtnn2JYezjIet/KvxFsNoOQF/HgznXPhRKPO14XSpDTxAkFVmm9ueJF+NqMh7OUhrfezZvBRRxCAzfwpU/DWN15JgQ7AfYpR97+d3PR57kOp8jjcpFIeOAFoA/klMeQv98PxE107wZfvO/zhE+T9eMq/vrrX1ObfSrbVmraaPYJNBHkAVhKGTigI69Uo8knEBmjyz3hiQ3mDJ17QsQ1mnsCkWlTT5fr4ICAGjD1BAIK8pev108hLlQKMVzmCTQRlBy0fsrAfgoOuNHEE4iM9pSkubzTnXVVBs874QBV807kuNvSV+IJBOrRBKnNO43YccEBMZ13AlFVxB/LaaZoLr7pQ22VqmbTLF+k83QVJr+lOyUpA/8t8nyr1agmiar535JiKqWz8KvyWe9MN8GBDq2ncroJXvdq6x1NN52cR3pfgAZKV1+vc8ooql6DCDWvQeRiwDXFPVmIuNY3HUdahtah4QGpmAA7wPzcXxgNCoIPRvurcCkUHYqoiaZSogaEmHUQXglApLLHL6mbBhhVcvliUjcECv9eMe0AJZeINImnkA0SEyHtgVBLPMNV47q+g2vQ8AHeQS4BieeFo86PD/N1AO7PB2nR+52VLNWcrwbSdVidX3ADcb7vECBjqDgk6Alynm3iKBuKeqc1nAZAwLQ32vBsE0fneoWqn84ohBquJN8/zgYZ7KBmLQBAKEVz9yDLGM9o1fwOE7t1jCWNNwMLLDIxnXk9d4lY09f7pH7OpsNmUx4KmvN5cQEPo0O7VKukr4PT0teDci61S7URcy7x+Rg5lxpdv1nOPQewoJJf05x77jVav5xL/GbFmHnOPaHb74o4F5PTStuH5dz2JYvlXOOcK6fBMXIua0/mWs69CM7FmIyPc9llrXPlCePjXLBb2XSl5zoT9/FzP+Wex67MMRZ7DtSXAmRAEG/AkwPZ2N4KtcDu7Ct2CTFz3NKjWraFgGLcQfsemQ3IjCB3rnimCyRAI/2QLZDHG+nt/pv/fK6RXk4XSIFl7qDdkKy9Ncq2Q45+sdKNreLY2LIFKhW4Xp+G+PS1Ps2gPZLtHUbWpxnMpyG+9Gn8EkhqyxTAqRm0X5K1h0XNLaN7WkFfpl8zUMMk4Wo2OzRMFq51h2MzaL8kNxqKtQ2T74YXdzB9jWszdOskPyEoe61bdsIKA7bs7Lx0R7Jlp9pLndRmN87qdfenbtvJmHvCaOfbuhPWMBQMv17P3HN9h6PSpoz1PVqbvTrg1p19uebcbt05gh5Rz6UO7kBJMODOnTBMjG5tZXfufBe25LKPl3burKdP3drOnU2ojan/k9s9sUrW9XnHxtDGc1ncbo5lPu4T4E6IGM5k8dHtkoUnNuIzcCYrCLogajqPxY3uj2XzWP0hy3QWi9stlMqeKu+uuTCcxPLtbkrmnZkp49UcllcDieEcFnhfa9M5rK31aIa/2RyqprBqODWdwuq4t7dNYY3eq5kyMvIMFniD8Ov1bAh6bc3xoA1pHffMtq6N8YY0gpBDO8BjvDXNt+3gF84nxOVjrvb0L6wx3O2sxiTN2X7QdjXf1s6WrYXYa8MOw3KzrZ0dMTcjykbOzXZ7tAvnZkTxmLk5gMKi4+VmRN0xc3Nw7nb8y+bmKvLdIKi3LBgm56A9jGbJ2TQ54+rdsF23Dh7j5BwcvZWpJedRkzOrZpebEDNNzlBgb7Tk7Hfl6o0zs607rFSVBq+uax+WmW3p4XiZWaLH66x3h25TMSwzHy1LtMw8amaeen7QPUFhw9R87o06+103ez7vUiewx9WZ2FkeZqm6Ol5e+yy/+uL3NBLqjP8D -------------------------------------------------------------------------------- /doc/map-nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/map-nodes.png -------------------------------------------------------------------------------- /doc/rewrite-clj-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/rewrite-clj/1a145e6dc51505bb8541115f7772bb84bf6be51f/doc/rewrite-clj-logo.png -------------------------------------------------------------------------------- /fig.cljs.edn: -------------------------------------------------------------------------------- 1 | ;; figwheel main config 2 | ^{:watch-dirs ["test" "src"] 3 | :auto-testing true 4 | :open-url "http://[[server-hostname]]:[[server-port]]/figwheel-extra-main/auto-testing"} 5 | {:main rewrite-clj.hello-figwheel 6 | :warnings {:fn-deprecated false 7 | :single-segment-namespace false}} 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rewrite-clj", 3 | "devDependencies": { 4 | "karma": "^6.4.4", 5 | "karma-chrome-launcher": "^3.2.0", 6 | "karma-cljs-test": "^0.1.0", 7 | "karma-junit-reporter": "^2.0.0", 8 | "karma-spec-reporter": "^0.0.36", 9 | "shadow-cljs": "^3.1.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /resources/clj-kondo.exports/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /script/apply_import_vars.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns apply-import-vars 4 | (:require [helper.main :as main] 5 | [helper.shell :as shell] 6 | [lread.status-line :as status])) 7 | 8 | (def args-usage "Valid args: (gen-code|check|--help) 9 | 10 | Commands: 11 | gen-code Generate API sources from templates 12 | check Fail if API sources are stale as compared to templates 13 | 14 | Options: 15 | --help Show this help") 16 | 17 | (defn -main[& args] 18 | (when-let [opts (main/doc-arg-opt args-usage args)] 19 | (let [cmd (if (get opts "check") "check" "gen-code")] 20 | (status/line :head (str "Running apply import vars " cmd)) 21 | (shell/command "clojure -X:apply-import-vars:script" cmd))) 22 | nil) 23 | 24 | (main/when-invoked-as-script 25 | (apply -main *command-line-args*)) 26 | -------------------------------------------------------------------------------- /script/ci_publish.clj: -------------------------------------------------------------------------------- 1 | (ns ci-publish 2 | "Publish work we invoke from GitHub Actions. 3 | Separated out here: 4 | - to make it clear what is happening on ci 5 | - rate of change here should be less/different than in publish namespace" 6 | (:require [babashka.tasks :as t] 7 | [build-shared] 8 | [lread.status-line :as status])) 9 | 10 | (def ^:private changelog-url (format "https://github.com/%s/blob/main/CHANGELOG.adoc" (build-shared/lib-github-coords))) 11 | 12 | (defn- assert-on-ci [] 13 | (when (not (System/getenv "CI")) 14 | (status/die 1 "to be run from continuous integration server only"))) 15 | 16 | (defn- ci-tag [] 17 | (when (= "tag" (System/getenv "GITHUB_REF_TYPE")) 18 | (System/getenv "GITHUB_REF_NAME"))) 19 | 20 | (defn- analyze-ci-tag [] 21 | (let [tag (ci-tag)] 22 | (if (not tag) 23 | (status/die 1 "CI tag not found") 24 | (let [version-from-tag (build-shared/tag->version tag) 25 | lib-version (build-shared/lib-version)] 26 | (cond 27 | (not version-from-tag) 28 | (status/die 1 "Not recognized as version tag: %s" tag) 29 | 30 | (not= version-from-tag lib-version) 31 | (status/die 1 "Lib version %s does not match version from tag %s" 32 | lib-version version-from-tag) 33 | :else 34 | {:tag tag 35 | :version lib-version}))))) 36 | 37 | ;; 38 | ;; Task entry points 39 | ;; 40 | 41 | (defn clojars-deploy [] 42 | (assert-on-ci) 43 | (analyze-ci-tag) ;; fail on unexpected version tag 44 | (t/shell "clojure -T:build:deploy deploy")) 45 | 46 | (defn github-create-release [] 47 | (assert-on-ci) 48 | (let [{:keys [tag]} (analyze-ci-tag)] 49 | (t/shell "gh release create" 50 | tag 51 | "--title" tag 52 | "--notes" (format "[Changelog](%s#%s)" changelog-url tag)))) 53 | 54 | (defn cljdoc-request-build [] 55 | (assert-on-ci) 56 | (let [{:keys [version]} (analyze-ci-tag) 57 | lib (build-shared/lib-artifact-name)] 58 | (status/line :head "Informing cljdoc of %s version %s" lib version) 59 | (assert-on-ci) 60 | (let [exit-code (-> (t/shell {:continue true} 61 | "curl" "-X" "POST" 62 | "-d" (str "project=" lib) 63 | "-d" (str "version=" version) 64 | "https://cljdoc.org/api/request-build2") 65 | :exit)] 66 | (when (not (zero? exit-code)) 67 | (status/line :warn (str "Informing cljdoc did not seem to work, exited with " exit-code)))))) 68 | -------------------------------------------------------------------------------- /script/clj_graal/template/by_ns_test_runner.clj.template: -------------------------------------------------------------------------------- 1 | ;; auto-generated test runner to support running of tests under graal 2 | (ns 3 | clj-graal.test-runner 4 | (:gen-class) 5 | (:require 6 | [clojure.test :as t] 7 | #_@TEST_NSES_HERE)) 8 | 9 | (defn 10 | -main 11 | [& _args] 12 | (println "clojure version" (clojure-version)) 13 | (println "java version" (System/getProperty "java.version")) 14 | (println 15 | "running native?" 16 | (= "executable" (System/getProperty "org.graalvm.nativeimage.kind"))) 17 | (let 18 | [{:keys [fail error]} 19 | (apply 20 | t/run-tests 21 | '(#_@TEST_NSES_HERE))] 22 | (System/exit (if (zero? (+ fail error)) 0 1)))) 23 | -------------------------------------------------------------------------------- /script/clj_graal/template/by_var_test_runner.clj.template: -------------------------------------------------------------------------------- 1 | ;; template for graal test runner 2 | 3 | ;; clojure.test builds up what tests to run at runtime, this causes native-image to use more 4 | ;; memory than is available on CircleCI, so here we pre-find our test vars to run and 5 | ;; explicitly list them 6 | 7 | (ns clj-graal.test-runner 8 | (:gen-class) 9 | (:require [clojure.test :as t] 10 | #_@TEST_NSES_HERE)) 11 | 12 | (defn test-ns-vars [[ns-obj ns-vars]] 13 | (binding [t/*report-counters* (ref t/*initial-report-counters*)] 14 | (t/do-report {:type :begin-test-ns, :ns ns-obj}) 15 | (t/test-vars ns-vars) 16 | (t/do-report {:type :end-test-ns, :ns ns-obj}) 17 | @t/*report-counters*)) 18 | 19 | (defn run-test-vars 20 | ([& test-vars] 21 | (let [ns-vars (group-by (comp :ns meta) test-vars) 22 | summary (assoc (apply merge-with + (map test-ns-vars ns-vars)) 23 | :type :summary)] 24 | (t/do-report summary) 25 | summary))) 26 | 27 | (defn -main [& _args] 28 | #_(println "clojure version" (clojure-version)) 29 | #_(println "java version" (System/getProperty "java.version")) 30 | #_(println "running native?" (= "executable" (System/getProperty "org.graalvm.nativeimage.kind"))) 31 | (let [summary (run-test-vars #_@TEST_VARS_HERE)] 32 | (if (t/successful? summary) 0 1))) 33 | -------------------------------------------------------------------------------- /script/clj_graal/template/direct_runner.clj.template: -------------------------------------------------------------------------------- 1 | ;; template for graal test runner 2 | 3 | ;; clojure.test builds up what tests to run at runtime, this causes native-image to use more 4 | ;; memory than is available on CircleCI, so here we pre-find our test vars to run and 5 | ;; explicitly list them 6 | 7 | (ns clj-graal.test-runner 8 | (:gen-class) 9 | (:require #_@TEST_NSES_HERE)) 10 | 11 | (defn -main [& _args] 12 | (println "clojure version" (clojure-version)) 13 | (println "java version" (System/getProperty "java.version")) 14 | (println "running native?" (= "executable" (System/getProperty "org.graalvm.nativeimage.kind"))) 15 | (println "running tests without clojure.test to see impact on graalvm mem usage") 16 | #_@TEST_FNS_HERE) 17 | -------------------------------------------------------------------------------- /script/code_info/ns_lister.clj: -------------------------------------------------------------------------------- 1 | (ns code-info.ns-lister 2 | (:require [cli-matic.core :as cli-matic] 3 | [clojure.java.io :as io] 4 | [clojure.string :as string] 5 | [clojure.tools.namespace.find :as find])) 6 | 7 | ;; 8 | ;; to run generated test runner: 9 | ;; java -cp "target/clj-graal/generated:$(clojure -M:test-common -Spath)" clojure.main -m clj-graal.test-runner 10 | ;; 11 | 12 | (defn- nses[lang] 13 | (->> (find/find-namespaces [(io/file "test")] (if (= lang "cljs") 14 | find/cljs 15 | find/clj)) 16 | (filter #(re-matches #".*-test" (str %))))) 17 | 18 | ;; NOTE this will not work for cljs 19 | (defn- vars[test-nses] 20 | (->> test-nses 21 | (mapcat (fn [ns] 22 | (require ns) 23 | (->> (the-ns ns) 24 | ns-publics 25 | vals 26 | (filter #(:test (meta %)))))))) 27 | 28 | (defn- cmd-find-all-vars[{:keys [lang] :as _opts}] 29 | (->> (nses lang) 30 | vars 31 | (map symbol) 32 | sort 33 | (string/join " ") 34 | println)) 35 | 36 | (defn- cmd-find-all-namespaces[{:keys [lang] :as _opts}] 37 | (->> (nses lang) 38 | sort 39 | (string/join " ") 40 | println)) 41 | 42 | (def climatic-config 43 | {:app {:command "ns-lister" 44 | :description "lists test namespaces and tests" 45 | :version "0.0.0"} 46 | :global-opts [{:option "lang" 47 | :as "clj or cljs" 48 | :type :string 49 | :default "clj"}] 50 | :commands [{:command "find-all-vars" 51 | :description "Return a list of all test vars" 52 | :runs cmd-find-all-vars} 53 | {:command "find-all-namespaces" 54 | :description "Return a list of all test namespaces" 55 | :runs cmd-find-all-namespaces}]}) 56 | 57 | (defn -main [& args] 58 | (cli-matic/run-cmd args climatic-config)) 59 | -------------------------------------------------------------------------------- /script/dev_repl.clj: -------------------------------------------------------------------------------- 1 | (ns dev-repl 2 | (:require [babashka.cli :as cli] 3 | [babashka.process :as process] 4 | [clojure.string :as str] 5 | [lread.status-line :as status])) 6 | 7 | (def cli-spec {:help {:desc "This usage help"} 8 | 9 | :flowstorm {:alias :f 10 | :coerce :boolean 11 | :desc "Enable flowstorm"} 12 | 13 | ;; cider nrepl pass through opts 14 | :host {:ref "" 15 | :alias :h 16 | :default "127.0.0.1" 17 | :desc "Host address"} 18 | :bind {:ref "" 19 | :alias :b 20 | :default "127.0.0.1" 21 | :desc "Bind address"} 22 | :port {:ref "" 23 | :coerce :int 24 | :default 0 25 | :alias :p 26 | :desc "Port, 0 for auto-select"}}) 27 | 28 | (defn- usage-help[] 29 | (status/line :head "Usage help") 30 | (status/line :detail (cli/format-opts {:spec cli-spec :order [:flowstorm :host :bind :port :help]}))) 31 | 32 | (defn- usage-fail [msg] 33 | (status/line :error msg) 34 | (usage-help) 35 | (System/exit 1)) 36 | 37 | (defn- parse-opts [args] 38 | (let [opts (cli/parse-opts args {:spec cli-spec 39 | :restrict true 40 | :error-fn (fn [{:keys [msg]}] 41 | (usage-fail msg))})] 42 | (when-let [extra-gunk (-> (meta opts) :org.babashka/cli)] 43 | (usage-fail (str "unrecognized on the command line: " (pr-str extra-gunk)))) 44 | opts)) 45 | 46 | 47 | (defn launch-repl [flavor args] 48 | (let [{:keys [flowstorm host bind port help]} (parse-opts args)] 49 | (if help 50 | (usage-help) 51 | (let [aliases (cond-> [(case flavor 52 | :cljs "nrepl/cljs:cljs" 53 | :jvm "nrepl/jvm")] 54 | flowstorm (conj "flowstorm"))] 55 | (status/line :head "Launching Clojure %s nREPL" (name flavor)) 56 | (when flowstorm 57 | (status/line :detail "Flowstorm support is enabled")) 58 | (process/exec "clj" (str "-M:1.12:test-common:nrepl:" (str/join ":" aliases)) 59 | "-h" host 60 | "-b" bind 61 | "-p" port))))) 62 | 63 | ;; Entry points 64 | (defn dev-jvm [& args] 65 | (launch-repl :jvm args)) 66 | 67 | 68 | (defn dev-cljs [& args] 69 | (launch-repl :cljs args)) 70 | -------------------------------------------------------------------------------- /script/download_deps.clj: -------------------------------------------------------------------------------- 1 | (ns download-deps 2 | (:require [babashka.tasks :as t])) 3 | 4 | ;; clojure has a -P command, but to bring down all deps we need to specify all aliases 5 | ;; bb deps will be brought down just from running bb (which assumedly is how this code is run) 6 | 7 | (defn -main [& _args] 8 | ;; do all the work from build.clj to avoid repeated JVM launch costs 9 | (t/clojure "-T:build download-deps")) 10 | -------------------------------------------------------------------------------- /script/gen-user-guide-reader-macro-table.clj: -------------------------------------------------------------------------------- 1 | ;; run via: clojure -M 2 | ;; generates user guide reader macro example table, see user guide for comment 3 | (require '[rewrite-clj.node :as n] 4 | '[rewrite-clj.parser :as p]) 5 | 6 | (->> [["Quote" ["'form"]] 7 | ["Character" ["\\newline" "\\space" "\\tab"]] 8 | ["Comment" ["; comment"]] 9 | ["Deref" ["@form"]] 10 | ["Metadata" ["^{:a 1 :b 2} [1 2 3]" 11 | "^String x" 12 | "^:dynamic x"]] 13 | ["Set" ["#{1 2 3}"]] 14 | ["Regex" ["#\"reg.*ex\""]] 15 | ["Var-quote" ["#'x"]] 16 | ["Anonymous function" ["#(println %)"]] 17 | ["Ignore next form" ["#_ :ignore-me"]] 18 | ["Syntax quote" ["`symbol"]] 19 | ["Syntax unquote" ["~symbol"]] 20 | ["Tagged literal" ["#foo/bar [1 2 3]" 21 | "#inst \"2018-03-28T10:48:00.000\"" 22 | "#uuid \"3b8a31ed-fd89-4f1b-a00f-42e3d60cf5ce\""]] 23 | ["Reader conditional" ["#?(:clj x :cljs y)" 24 | "#@?(:clj [x] :cljs [y])"]]] 25 | (reduce (fn [sdoc [desc inputs]] 26 | (let [parsed (map p/parse-string inputs) 27 | tag (-> parsed first n/tag) 28 | _ (assert (every? #(= (n/tag %) tag) parsed) "oops, expected tags to be same") 29 | outputs (map #(try (n/sexpr %) (catch Exception _e nil)) 30 | parsed) 31 | example-rows (map #(format "a|`%s`\na|%s\n" %1 32 | (if %2 33 | (str "`" (binding [*print-meta* true] 34 | (pr-str %2)) "`") 35 | "")) 36 | inputs outputs)] 37 | (str sdoc 38 | (format "2+a|*%s* `%s`\n" desc tag) 39 | (apply str example-rows) 40 | "\n"))) 41 | "") 42 | (format "|===\n| Parsed input | Node sexpr\n\n%s|===") 43 | println) 44 | -------------------------------------------------------------------------------- /script/helper/deps_patcher.clj: -------------------------------------------------------------------------------- 1 | (ns helper.deps-patcher 2 | "Quick and dirty little project.clj and deps.edn patcher" 3 | (:require [rewrite-clj.node :as n] 4 | [rewrite-clj.zip :as z])) 5 | 6 | (defn- find-project-deps 7 | "Interested in top level deps only at this time." 8 | [zloc] 9 | (-> zloc 10 | z/down 11 | (z/find-value z/right :dependencies) 12 | z/next)) 13 | 14 | (defn- remove-project-deps [zloc dep-syms] 15 | (z/subedit->> (find-project-deps zloc) 16 | z/down 17 | (iterate (fn [zloc] 18 | (when (not (z/end? zloc)) 19 | (if (dep-syms (-> zloc z/down z/sexpr)) 20 | (-> zloc z/remove z/next) 21 | (-> zloc z/right))))) 22 | (take-while identity) 23 | last)) 24 | 25 | (defn- add-project-deps [zloc new-deps] 26 | (let [zloc-deps (find-project-deps zloc) 27 | indent (n/spaces (-> zloc-deps z/node meta :col))] 28 | (reduce (fn [zloc dep] 29 | (-> zloc 30 | (z/append-child* (n/newlines 1)) 31 | (z/append-child* indent) 32 | (z/append-child dep))) 33 | zloc-deps 34 | new-deps))) 35 | 36 | (defn update-project-deps [{:keys [filename additions removals] :as kwargs}] 37 | (println kwargs) 38 | (-> filename 39 | z/of-file 40 | (remove-project-deps removals) 41 | z/root 42 | z/edn* 43 | z/next 44 | (add-project-deps additions) 45 | z/root-string 46 | (->> (spit filename)))) 47 | 48 | (defn- remove-deps-deps [zloc dep-syms] 49 | (reduce (fn [zloc sym] 50 | (if (z/get zloc sym) 51 | (z/subedit-> zloc (z/get sym) z/remove z/remove) 52 | zloc)) 53 | (z/get zloc :deps) 54 | dep-syms)) 55 | 56 | (defn- add-deps-deps [zloc new-deps] 57 | (let [zloc-deps (z/get zloc :deps) 58 | indent (n/spaces (-> zloc-deps z/node meta :col))] 59 | (reduce (fn [zloc [k v]] 60 | (-> zloc 61 | (z/append-child* (n/newlines 1)) 62 | (z/append-child* indent) 63 | (z/append-child k) 64 | (z/append-child v))) 65 | zloc-deps 66 | new-deps))) 67 | 68 | (defn update-deps-deps [{:keys [filename additions removals] :as kwargs}] 69 | (println kwargs) 70 | (-> filename 71 | z/of-file 72 | (remove-deps-deps removals) 73 | z/root 74 | z/edn* 75 | z/next 76 | (add-deps-deps additions) 77 | z/root-string 78 | (->> (spit filename)))) 79 | -------------------------------------------------------------------------------- /script/helper/env.clj: -------------------------------------------------------------------------------- 1 | (ns helper.env 2 | (:require [clojure.edn :as edn] 3 | [helper.shell :as shell] 4 | [lread.status-line :as status] 5 | [version-clj.core :as ver])) 6 | 7 | (defn- assert-clojure-min-version 8 | "Asserts minimum version of Clojure version" 9 | [] 10 | (let [min-version "1.10.1.697" 11 | version 12 | (->> (shell/command {:out :string} "clojure -Sdescribe") 13 | :out 14 | edn/read-string 15 | :version)] 16 | (when (< (ver/version-compare version min-version) 0) 17 | (status/die 1 18 | "A minimum version of Clojure %s required.\nFound version: %s" 19 | min-version version)))) 20 | 21 | (defn assert-min-versions[] 22 | (assert-clojure-min-version)) 23 | -------------------------------------------------------------------------------- /script/helper/jdk.clj: -------------------------------------------------------------------------------- 1 | (ns helper.jdk 2 | (:require [helper.shell :as shell])) 3 | 4 | (defn version 5 | "Returns jdk version and major version with appropriate conversion. (ex 1.8 returns major of 8)" 6 | [] 7 | (let [raw-version (->> (shell/command {:err :string} 8 | "java -version") 9 | :err 10 | (re-find #"version \"(.*)\"") 11 | last) 12 | major-minor (->> raw-version 13 | (re-find #"(\d+)(?:\.(\d+))?.*") 14 | rest 15 | (map #(when % (Integer/parseInt %))))] 16 | {:version raw-version 17 | :major (if (= (first major-minor) 1) 18 | (second major-minor) 19 | (first major-minor))})) 20 | -------------------------------------------------------------------------------- /script/helper/main.clj: -------------------------------------------------------------------------------- 1 | (ns helper.main 2 | (:require [clojure.string :as string] 3 | [docopt.core :as docopt] 4 | [helper.env :as env] 5 | [lread.status-line :as status])) 6 | 7 | (defmacro when-invoked-as-script 8 | "Runs `body` when clj was invoked from command line as a script." 9 | [& body] 10 | `(when (= *file* (System/getProperty "babashka.file")) 11 | ~@body)) 12 | 13 | (defn- args-usage-to-docopt-usage 14 | "We specify 15 | 16 | Valid args: 17 | cmd1 18 | cmd2 19 | --opt1 20 | 21 | Or: 22 | 23 | Valid args: [options] 24 | 25 | But docopt expects: 26 | 27 | Usage: 28 | foo cmd1 29 | foo cmd2 30 | foo -opt1 31 | 32 | Or: 33 | 34 | Usage: [options] 35 | 36 | This little fn converts from our args usage to something docopt can understand" 37 | [usage] 38 | (let [re-arg-usage #"(?msi)^Valid args:.*?(?=\n\n)"] 39 | (if-let [args-usage (re-find re-arg-usage usage)] 40 | (let [[label-line & variant-lines] (-> args-usage string/split-lines) 41 | docopt-usage-block (string/join "\n" (concat [(if (re-find #"(?i)Valid args:( +\S.*)" label-line) 42 | (string/replace label-line #"(?i)Valid args:( +\S.*)" "Usage: foo $1") 43 | "Usage:")] 44 | (map #(string/replace % #"^ " " foo ") variant-lines))) 45 | docopt-usage-block (str docopt-usage-block "\n")] 46 | (string/replace usage re-arg-usage docopt-usage-block)) 47 | (throw (ex-info "Did not find expected 'Valid args:' in usage" {}))))) 48 | 49 | (def default-arg-usage "Valid args: [--help] 50 | 51 | This command accepts no arguments.") 52 | 53 | (defn doc-arg-opt 54 | "Args usage wrapper for docopt. 55 | 56 | You'll need to specify --help in your arg-usage, but code to handle --help is provided here." 57 | ([args] 58 | (doc-arg-opt default-arg-usage args)) 59 | ([arg-usage args] 60 | (env/assert-min-versions) 61 | (let [opts (docopt/docopt (args-usage-to-docopt-usage arg-usage) 62 | args 63 | identity 64 | (fn usage-error [_docopt-usage] (status/die 1 arg-usage)))] 65 | (if (get opts "--help") 66 | (do 67 | (status/line :detail arg-usage) 68 | nil) 69 | opts)))) 70 | -------------------------------------------------------------------------------- /script/helper/os.clj: -------------------------------------------------------------------------------- 1 | (ns helper.os 2 | (:require [clojure.string :as string])) 3 | 4 | (defn get-os [] 5 | (let [os-name (string/lower-case (System/getProperty "os.name"))] 6 | (condp re-find os-name 7 | #"win" :win 8 | #"mac" :mac 9 | #"(nix|nux|aix)" :unix 10 | #"sunos" :solaris 11 | :unknown))) 12 | -------------------------------------------------------------------------------- /script/helper/shell.clj: -------------------------------------------------------------------------------- 1 | (ns helper.shell 2 | (:require [babashka.tasks :as tasks] 3 | [clojure.pprint :as pprint] 4 | [lread.status-line :as status])) 5 | 6 | (def default-opts {:error-fn 7 | (fn die-on-error [{{:keys [exit cmd]} :proc}] 8 | (status/die exit 9 | "shell exited with %d for: %s" 10 | exit 11 | (with-out-str (pprint/pprint cmd))))}) 12 | 13 | (defn command 14 | "Thin wrapper on babashka.tasks/shell that on error, prints status error message and exits. 15 | Automatically converts calls to clojure on Windows to call with powershell" 16 | [cmd & args] 17 | (let [[opts cmd args] (if (map? cmd) 18 | [cmd (first args) (rest args)] 19 | [nil cmd args]) 20 | opts (merge opts default-opts)] 21 | (apply tasks/shell opts cmd args))) 22 | 23 | (defn clojure 24 | "Wrap tasks/clojure for my loud error reporting treatment" 25 | [& args] 26 | (let [[opts args] (if (map? (first args)) 27 | [(first args) (rest args)] 28 | [nil args]) 29 | opts (merge opts default-opts)] 30 | (apply tasks/clojure opts args))) 31 | -------------------------------------------------------------------------------- /script/lint.clj: -------------------------------------------------------------------------------- 1 | (ns lint 2 | (:require [helper.main :as main] 3 | [lint-eastwood :as eastwood] 4 | [lint-kondo :as kondo])) 5 | 6 | (defn -main [& args] 7 | (when (main/doc-arg-opt kondo/args-usage args) 8 | (apply kondo/-main args) 9 | (eastwood/-main))) 10 | 11 | (main/when-invoked-as-script 12 | (apply -main *command-line-args*)) 13 | -------------------------------------------------------------------------------- /script/lint_eastwood.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns lint-eastwood 4 | (:require [helper.main :as main] 5 | [helper.shell :as shell] 6 | [lread.status-line :as status])) 7 | 8 | (defn- lint [] 9 | (status/line :head "eastwood: linting") 10 | (shell/command "clojure -M:test-common:eastwood")) 11 | 12 | (defn -main [& args] 13 | (when (main/doc-arg-opt args) 14 | (lint)) 15 | nil) 16 | 17 | (main/when-invoked-as-script 18 | (apply -main *command-line-args*)) 19 | -------------------------------------------------------------------------------- /script/lint_kondo.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns lint-kondo 4 | (:require [babashka.classpath :as bbcp] 5 | [babashka.fs :as fs] 6 | [clojure.string :as string] 7 | [helper.main :as main] 8 | [helper.shell :as shell] 9 | [lread.status-line :as status])) 10 | 11 | (def clj-kondo-cache ".clj-kondo/.cache") 12 | 13 | (defn- cache-exists? [] 14 | (fs/exists? clj-kondo-cache)) 15 | 16 | (defn- delete-cache [] 17 | (when (cache-exists?) 18 | (fs/delete-tree clj-kondo-cache))) 19 | 20 | (defn- build-cache [] 21 | (when (cache-exists?) 22 | (delete-cache)) 23 | (let [clj-cp (-> (shell/clojure {:out :string} 24 | "-Spath -M:test") 25 | with-out-str 26 | string/trim) 27 | bb-cp (bbcp/get-classpath)] 28 | (status/line :detail "- copying lib configs and creating cache") 29 | (shell/command "clojure -M:clj-kondo --skip-lint --copy-configs --dependencies --lint" clj-cp bb-cp))) 30 | 31 | (defn- check-cache [{:keys [rebuild-cache]}] 32 | (status/line :head "clj-kondo: cache check") 33 | (if-let [rebuild-reason (cond 34 | rebuild-cache 35 | "Rebuild requested" 36 | 37 | (not (cache-exists?)) 38 | "Cache not found" 39 | 40 | :else 41 | (let [updated-dep-files (fs/modified-since clj-kondo-cache ["deps.edn" "bb.edn"])] 42 | (when (seq updated-dep-files) 43 | (format "Found deps files newer than lint cache: %s" (mapv str updated-dep-files)))))] 44 | (do (status/line :detail rebuild-reason) 45 | (build-cache)) 46 | (status/line :detail "Using existing cache"))) 47 | 48 | (defn- lint [opts] 49 | (check-cache opts) 50 | (status/line :head "clj-kondo: linting") 51 | (let [{:keys [exit]} 52 | (shell/command {:continue true} 53 | "clojure -M:clj-kondo --parallel --lint src test script deps.edn build.clj")] 54 | (cond 55 | (= 2 exit) (status/die exit "clj-kondo found one or more lint errors") 56 | (= 3 exit) (status/die exit "clj-kondo found one or more lint warnings") 57 | (> exit 0) (status/die exit "clj-kondo returned unexpected exit code")))) 58 | 59 | (def args-usage "Valid args: [options] 60 | 61 | Options: 62 | --rebuild Force rebuild of clj-kondo lint cache. 63 | --help Show this help.") 64 | 65 | (defn -main [& args] 66 | (when-let [opts (main/doc-arg-opt args-usage args)] 67 | (lint {:rebuild-cache (get opts "--rebuild")}))) 68 | 69 | (main/when-invoked-as-script 70 | (apply -main *command-line-args*)) 71 | -------------------------------------------------------------------------------- /script/outdated.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns outdated 4 | (:require [clojure.java.io :as io] 5 | [helper.main :as main] 6 | [helper.shell :as shell] 7 | [lread.status-line :as status])) 8 | 9 | (defn check-clojure [] 10 | (status/line :head "Checking Clojure deps") 11 | (shell/command {:continue true} 12 | "clojure -M:outdated")) 13 | 14 | (defn check-nodejs [] 15 | (status/line :head "Checking Node.js deps") 16 | (when (not (.exists (io/file "node_modules"))) 17 | (status/line :detail "node_modules, not found, installing.") 18 | (shell/command "npm ci")) 19 | 20 | (let [{:keys [:exit]} (shell/command {:continue true} 21 | "npm outdated")] 22 | (when (zero? exit) 23 | (status/line :detail "All Node.js dependencies seem up to date.") 24 | (status/line :detail "(warning: deps are only checked against installed ./node_modules)")))) 25 | 26 | (defn -main[& args] 27 | (when (main/doc-arg-opt args) 28 | (check-clojure) 29 | (check-nodejs)) 30 | nil) 31 | 32 | (main/when-invoked-as-script 33 | (apply -main *command-line-args*)) 34 | -------------------------------------------------------------------------------- /script/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %date %-5level %logger{36} - %message%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /script/sci_test_gen_publics.clj: -------------------------------------------------------------------------------- 1 | ;; Run via clojure in service of sci native tests 2 | (ns sci-test-gen-publics 3 | (:require [clojure.java.io :as io] 4 | [clojure.pprint :as pprint] 5 | [clojure.string :as string] 6 | [clojure.tools.namespace.find :as find])) 7 | 8 | (defn matches-some-pat [spats s] 9 | (let [pats (map re-pattern spats)] 10 | (some #(re-matches % s) pats))) 11 | 12 | (defn find-nses [{:keys [:dirs :exclude-ns-regexes]}] 13 | (->> (find/find-namespaces (map io/file dirs) find/clj) 14 | (remove #(matches-some-pat exclude-ns-regexes (str %))))) 15 | 16 | (defn find-pubs [nses {:keys [:exclude-var-regexes]}] 17 | (->> nses 18 | (map (fn [ns] 19 | (require ns) 20 | [ns (->> (ns-publics ns) 21 | vals 22 | (map meta) 23 | (remove #(matches-some-pat exclude-var-regexes (str (:name %)))) 24 | (sort-by :name))])) 25 | (filter #(seq (second %))) 26 | (into (sorted-map)))) 27 | 28 | (defn sci-ns-var-name [ns] 29 | (symbol (string/join "-" (string/split (str ns) #"\.")))) 30 | 31 | (defn gen-sci-ns-defs [nses] 32 | (map #(list 'def (sci-ns-var-name %) (list 'sci/create-ns (symbol (str "'" %)))) 33 | nses)) 34 | 35 | (defn gen-sci-ns-map-code [pubs {:keys [:fn-wrappers]}] 36 | (->> pubs 37 | (map (fn [[ns pubs]] 38 | [(symbol (str "'" ns)) 39 | (->> pubs 40 | (map (fn [pub] 41 | [(symbol (str "'" (:name pub))) 42 | (let [sym (str ns "/" (:name pub)) 43 | wrapper (ffirst (filter (fn [[_ pats]] (matches-some-pat pats sym)) 44 | fn-wrappers))] 45 | (if wrapper 46 | (list (symbol wrapper) 47 | (symbol sym)) 48 | (list (symbol 'sci/copy-var) 49 | (symbol sym) 50 | (symbol (sci-ns-var-name ns)))))])) 51 | (into (sorted-map)))])) 52 | (into (sorted-map)))) 53 | 54 | (defn -main [ & _args ] 55 | (let [opts {:dirs ["src"] 56 | :exclude-ns-regexes ["rewrite-clj\\.node\\.coercer" ] 57 | :exclude-var-regexes [".*Node.*"] 58 | :fn-wrappers {'import/fn-out-to-sci-out [".*/print" ".*/print-root"]}} 59 | ns-name "lib-under-sci-test.defs" 60 | pubs (-> (find-nses opts) 61 | (find-pubs opts)) 62 | nses (->> (keys pubs) 63 | (map #(symbol (str %))) 64 | sort) 65 | code (concat 66 | (list (list 'ns (symbol ns-name) 67 | (concat (list ':require 68 | '[sci.core :as sci] 69 | '[sci-test.import :as import]) 70 | nses))) 71 | (gen-sci-ns-defs nses) 72 | (list (list 'def 'namespaces (gen-sci-ns-map-code pubs opts))))] 73 | (binding [pprint/*print-right-margin* 130 74 | pprint/*print-miser-width* 130] 75 | (let [out-file (io/file "target/generated/sci-test/src" (str (string/escape ns-name {\- "_" \. "/"}) ".clj"))] 76 | (io/make-parents out-file) 77 | (io/delete-file out-file true) 78 | (run! #(pprint/pprint % (io/writer out-file :append true)) code) 79 | (println "Generated:" (str out-file))))) 80 | (System/exit 0)) 81 | -------------------------------------------------------------------------------- /script/sci_test_runner.clj: -------------------------------------------------------------------------------- 1 | ;; Interperted as part of sci native tests 2 | (ns sci-test-runner 3 | "Sci interpreted test runner, grabbed concepts I needed from cognitect.test-runner." 4 | (:require [clojure.java.io :as io] 5 | [clojure.test :as t] 6 | [clojure.tools.namespace.find :as find] 7 | [sci-test.test-runner :as test-runner])) 8 | 9 | (defn meta-test [v {:keys [include-with-meta exclude-with-meta]}] 10 | (and (if include-with-meta 11 | ((apply some-fn include-with-meta) (meta v)) 12 | true) 13 | (if exclude-with-meta 14 | ((complement (apply some-fn exclude-with-meta)) (meta v)) 15 | true))) 16 | 17 | (defn ns-filter [{:keys [namespace namespace-regex] :as opts}] 18 | (let [regexes (or namespace-regex [#".*\-test$"])] 19 | (fn [ns] 20 | (if namespace 21 | (namespace ns) 22 | (and (some #(re-matches % (name ns)) regexes) 23 | (meta-test ns opts)))))) 24 | 25 | (defn var-filter [opts] 26 | (fn [v] 27 | (meta-test v opts))) 28 | 29 | (defn find-test-nses [opts] 30 | (->> (find/find-namespaces (into [] (map io/file (:dirs opts))) find/clj) 31 | (filter (ns-filter opts)))) 32 | 33 | (defn find-test-vars [test-nses opts] 34 | (->> test-nses 35 | (mapcat (fn [ns] (->> ns 36 | ns-publics 37 | vals 38 | (filter #(get (meta %) :test)) 39 | (filter (var-filter opts))))))) 40 | 41 | (defn -main[] 42 | (let [opts {:exclude-with-meta [:skip-for-sci] 43 | :dirs ["test"]} 44 | nses (find-test-nses opts)] 45 | (println "Running tests with options:" opts) 46 | (dorun (map require nses)) 47 | (let [test-vars (find-test-vars nses opts) 48 | summary (test-runner/run-test-vars test-vars)] 49 | (System/exit (if (t/successful? summary) 0 1))))) 50 | 51 | (-main) 52 | -------------------------------------------------------------------------------- /script/test_clj.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-clj 4 | (:require [helper.main :as main] 5 | [helper.shell :as shell] 6 | [lread.status-line :as status])) 7 | 8 | (def allowed-clojure-versions '("1.8" "1.9" "1.10" "1.11" "1.12")) 9 | 10 | (defn run-unit-tests [clojure-version] 11 | (status/line :head (str "testing clojure source against clojure v" clojure-version)) 12 | (if (= "1.8" clojure-version) 13 | (shell/command "clojure" 14 | (str "-M:test-common:clj-test-runner:" clojure-version)) 15 | (shell/command "clojure" 16 | (str "-M:test-common:kaocha:" clojure-version) 17 | "--reporter" "documentation"))) 18 | 19 | (defn run-isolated-tests[clojure-version] 20 | (status/line :head (str "running isolated tests against clojure v" clojure-version)) 21 | (if (= "1.8" clojure-version) 22 | (shell/command "clojure" (str "-M:clj-test-runner:test-isolated:" clojure-version) 23 | "--dir" "test-isolated") 24 | (shell/command "clojure" (str "-M:kaocha:" clojure-version) 25 | "--profile" "test-isolated" 26 | "--reporter" "documentation"))) 27 | 28 | (def args-usage "Valid args: [options] 29 | 30 | Options: 31 | -v, --clojure-version VERSION Test with Clojure [1.8, 1.9, 1.10, 1.11, 1.12 all] [default: 1.8] 32 | --help Show this help") 33 | 34 | (defn -main [& args] 35 | (when-let [opts (main/doc-arg-opt args-usage args)] 36 | (let [clojure-version (get opts "--clojure-version")] 37 | 38 | (if (not (some #{clojure-version} (conj allowed-clojure-versions "all"))) 39 | (status/die 1 args-usage) 40 | (let [clojure-versions (if (= "all" clojure-version) 41 | allowed-clojure-versions 42 | [clojure-version])] 43 | (doseq [v clojure-versions] 44 | (run-unit-tests v) 45 | (run-isolated-tests v)))))) 46 | nil) 47 | 48 | (main/when-invoked-as-script 49 | (apply -main *command-line-args*)) 50 | -------------------------------------------------------------------------------- /script/test_clj_watch.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-clj-watch 4 | (:require [helper.env :as env] 5 | [helper.main :as main] 6 | [helper.shell :as shell] 7 | [lread.status-line :as status])) 8 | 9 | (defn -main [& args] 10 | ;; we simply pass along any args to kaocha, it will validate them 11 | (env/assert-min-versions) 12 | (status/line :head "launching kaocha watch on clojure sources") 13 | (apply shell/command "clojure -M:test-common:kaocha:1.9 --watch" args)) 14 | 15 | (main/when-invoked-as-script 16 | (apply -main *command-line-args*)) 17 | -------------------------------------------------------------------------------- /script/test_cljs_watch.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-cljs-watch 4 | (:require [helper.main :as main] 5 | [helper.shell :as shell] 6 | [lread.status-line :as status])) 7 | 8 | (defn -main [& args] 9 | (when (main/doc-arg-opt args) 10 | (status/line :detail "compiling code, then opening repl, afterwich your web browser will automatically open to figwheel test run summary") 11 | (shell/command "clojure -M:test-common:cljs:fig-test"))) 12 | 13 | (main/when-invoked-as-script 14 | (apply -main *command-line-args*)) 15 | -------------------------------------------------------------------------------- /script/test_coverage.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-coverage 4 | (:require [helper.main :as main] 5 | [helper.shell :as shell] 6 | [lread.status-line :as status])) 7 | 8 | (defn generate-doc-tests [] 9 | (status/line :head "Generating tests for code blocks in documents") 10 | (shell/command "clojure -X:test-doc-blocks gen-tests")) 11 | 12 | (defn run-clj-doc-tests [] 13 | (status/line :head "Running unit and code block tests under Clojure for coverage report") 14 | (shell/command "clojure" "-M:test-common:test-docs:kaocha" 15 | "--plugin" "cloverage" "--codecov" 16 | "--profile" "coverage" 17 | "--no-randomize" 18 | "--reporter" "documentation")) 19 | 20 | (defn -main [& args] 21 | (when (main/doc-arg-opt args) 22 | (generate-doc-tests) 23 | (run-clj-doc-tests)) 24 | nil) 25 | 26 | (main/when-invoked-as-script 27 | (apply -main *command-line-args*)) 28 | -------------------------------------------------------------------------------- /script/test_doc.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-doc 4 | (:require [babashka.fs :as fs] 5 | [helper.main :as main] 6 | [helper.shell :as shell] 7 | [lread.status-line :as status])) 8 | 9 | (defn generate-doc-tests [] 10 | (status/line :head "Generating tests for code blocks in documents") 11 | (shell/command "clojure -X:test-doc-blocks gen-tests")) 12 | 13 | (defn run-clj-doc-tests [] 14 | (status/line :head "Running code block tests under Clojure") 15 | (shell/command "clojure" "-M:test-common:test-docs:kaocha" 16 | "--profile" "test-docs" 17 | "--no-randomize" 18 | "--reporter" "documentation")) 19 | 20 | (defn run-cljs-doc-tests [] 21 | (status/line :head "Running code block tests under ClojureScript") 22 | (let [compile-opts {:warnings {:fn-deprecated false :single-segment-namespace false}} 23 | out-dir "target/cljsbuild/doc-tests" 24 | opts-fname (fs/file out-dir "doc-tests-opts.edn")] 25 | (fs/create-dirs out-dir) 26 | (spit opts-fname compile-opts) 27 | (shell/command "clojure" "-M:test-common:test-docs:cljs:cljs-test" 28 | "--compile-opts" opts-fname 29 | "--dir" "target/test-doc-blocks/test" 30 | "--out" out-dir))) 31 | 32 | (def allowed-platforms ["clj" "cljs"]) 33 | 34 | (def args-usage "Valid args: [options] 35 | 36 | Options: 37 | -p, --platform PLATFORM Test against [clj cljs all] [default: all] 38 | --help Show this help") 39 | 40 | (defn -main [& args] 41 | (when-let [opts (main/doc-arg-opt args-usage args)] 42 | (let [platform (get opts "--platform")] 43 | (if (not (some #{platform} (conj allowed-platforms "all"))) 44 | (status/die 1 args-usage) 45 | (let [platforms (if (= "all" platform) 46 | allowed-platforms 47 | [platform])] 48 | (generate-doc-tests) 49 | (doseq [p platforms] 50 | (case p 51 | "clj" (run-clj-doc-tests) 52 | "cljs" (run-cljs-doc-tests)))))))) 53 | 54 | (main/when-invoked-as-script 55 | (apply -main *command-line-args*)) 56 | -------------------------------------------------------------------------------- /script/test_jvm_sci.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-jvm-sci 4 | (:require [helper.main :as main] 5 | [helper.shell :as shell] 6 | [lread.status-line :as status])) 7 | 8 | (def allowed-clojure-versions '("1.11" "1.12")) 9 | 10 | (def args-usage "Valid args: [options] 11 | 12 | Options: 13 | -v, --clojure-version VERSION Test with Clojure [1.11, 1.12] [default: 1.12] 14 | --help Show this help") 15 | 16 | 17 | (defn validate-opts [opts] 18 | (when (not (some #{(get opts "--clojure-version")} allowed-clojure-versions)) 19 | (status/die 1 args-usage))) 20 | 21 | 22 | (defn -main [& args] 23 | (when-let [opts (main/doc-arg-opt args-usage args)] 24 | (validate-opts opts) 25 | (let [clojure-version (get opts "--clojure-version")] 26 | 27 | (status/line :head "Exposing rewrite-clj API to sci") 28 | (shell/command "clojure -M:script -m sci-test-gen-publics") 29 | 30 | (status/line :head "Interpreting tests with sci from using JVM using Clojure %s" clojure-version) 31 | (shell/command (format "clojure -M:sci-test:%s -m sci-test.main --file script/sci_test_runner.clj --classpath test" 32 | clojure-version)))) 33 | nil) 34 | 35 | (main/when-invoked-as-script 36 | (apply -main *command-line-args*)) 37 | -------------------------------------------------------------------------------- /script/test_native.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-native 4 | (:require [babashka.fs :as fs] 5 | [clojure.java.io :as io] 6 | [helper.graal :as graal] 7 | [helper.main :as main] 8 | [helper.os :as os] 9 | [helper.shell :as shell] 10 | [lread.status-line :as status])) 11 | 12 | (defn generate-test-runner [dir] 13 | (status/line :head "Generate test runner") 14 | (fs/delete-tree dir) 15 | (io/make-parents dir) 16 | (shell/command "clojure" "-M:script:test-common" 17 | "-m" "clj-graal.gen-test-runner" 18 | "--dest-dir" dir "test-by-namespace")) 19 | 20 | (def allowed-clojure-versions '("1.10" "1.11" "1.12")) 21 | 22 | (def args-usage "Valid args: [options] 23 | 24 | Options: 25 | -v, --clojure-version VERSION Test with Clojure [1.10, 1.11, 1.12] [default: 1.12] 26 | --help Show this help") 27 | 28 | 29 | (defn validate-opts [opts] 30 | (when (not (some #{(get opts "--clojure-version")} allowed-clojure-versions)) 31 | (status/die 1 args-usage))) 32 | 33 | (defn -main [& args] 34 | (when-let [opts (main/doc-arg-opt args-usage args)] 35 | (validate-opts opts) 36 | (graal/assert-min-version) 37 | (let [clojure-version (get opts "--clojure-version") 38 | native-image-xmx "6g" 39 | target-path "target" 40 | target-exe "rewrite-clj-test" 41 | full-target-exe (str target-path "/" target-exe (when (= :win (os/get-os)) ".exe"))] 42 | (status/line :head "Creating native image for test") 43 | (status/line :detail "java -version") 44 | (shell/command "java -version") 45 | (status/line :detail (str "\nnative-image max memory: " native-image-xmx)) 46 | (let [graal-native-image (graal/find-graal-native-image) 47 | test-runner-dir "target/generated/graal"] 48 | (graal/clean) 49 | (generate-test-runner test-runner-dir) 50 | (let [classpath (graal/compute-classpath (str "test-common:graal:native-test:" clojure-version))] 51 | (graal/aot-compile-sources classpath "clj-graal.test-runner") 52 | (graal/run-native-image {:graal-native-image graal-native-image 53 | :target-path target-path 54 | :target-exe target-exe 55 | :classpath classpath 56 | :native-image-xmx native-image-xmx 57 | :entry-class "clj_graal.test_runner"}))) 58 | (status/line :head "Native image built") 59 | (status/line :detail "built: %s, %d bytes" full-target-exe (.length (io/file full-target-exe))) 60 | (status/line :head "Running tests natively") 61 | (shell/command full-target-exe))) 62 | nil) 63 | 64 | (main/when-invoked-as-script 65 | (apply -main *command-line-args*)) 66 | -------------------------------------------------------------------------------- /script/test_native_sci.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-native-sci 4 | (:require [clojure.java.io :as io] 5 | [helper.graal :as graal] 6 | [helper.main :as main] 7 | [helper.os :as os] 8 | [helper.shell :as shell] 9 | [lread.status-line :as status])) 10 | 11 | (defn expose-api-to-sci [] 12 | (status/line :head "Expose rewrite-clj API to sci") 13 | (shell/command "clojure -M:script -m sci-test-gen-publics")) 14 | 15 | (defn generate-reflection-file [fname] 16 | (status/line :head "Generate reflection file for Graal native-image") 17 | (io/make-parents fname) 18 | (shell/command "clojure -M:sci-test:gen-reflection" fname) 19 | (status/line :detail fname)) 20 | 21 | (defn interpret-tests [exe-fname] 22 | (status/line :head "Interpreting tests with sci using natively compiled binary") 23 | (when (not (.exists (io/file exe-fname))) 24 | (status/die 1 "native image %s not found." exe-fname)) 25 | (shell/command exe-fname "--file" "script/sci_test_runner.clj" "--classpath" "test")) 26 | 27 | (def allowed-clojure-versions '("1.10" "1.11" "1.12")) 28 | 29 | (def args-usage "Valid args: [options] 30 | 31 | Options: 32 | -v, --clojure-version VERSION Test with Clojure [1.10, 1.11, 1.12] [default: 1.12] 33 | --help Show this help") 34 | 35 | 36 | (defn validate-opts [opts] 37 | (when (not (some #{(get opts "--clojure-version")} allowed-clojure-versions)) 38 | (status/die 1 args-usage))) 39 | 40 | (defn -main [& args] 41 | (when-let [opts (main/doc-arg-opt args-usage args)] 42 | (validate-opts opts) 43 | (graal/assert-min-version) 44 | (let [clojure-version (get opts "--clojure-version") 45 | native-image-xmx "6g" 46 | graal-reflection-fname "target/native-image/reflection.json" 47 | target-path "target" 48 | target-exe "sci-test-rewrite-clj" 49 | full-target-exe (str target-path "/" target-exe (when (= :win (os/get-os)) ".exe"))] 50 | (status/line :head "Creating native image for testing via sci") 51 | (status/line :detail "java -version") 52 | (shell/command "java -version") 53 | (status/line :detail (str "\nnative-image max memory: " native-image-xmx)) 54 | (let [graal-native-image (graal/find-graal-native-image)] 55 | (graal/clean) 56 | (expose-api-to-sci) 57 | (let [classpath (graal/compute-classpath (str "graal:sci-test:" clojure-version))] 58 | (graal/aot-compile-sources classpath "sci-test.main") 59 | (generate-reflection-file graal-reflection-fname) 60 | (graal/run-native-image {:graal-native-image graal-native-image 61 | :graal-reflection-fname graal-reflection-fname 62 | :target-path target-path 63 | :target-exe target-exe 64 | :classpath classpath 65 | :native-image-xmx native-image-xmx 66 | :entry-class "sci_test.main"}))) 67 | (status/line :head "build done") 68 | (status/line :detail "built: %s, %d bytes" full-target-exe (.length (io/file full-target-exe))) 69 | (interpret-tests full-target-exe))) 70 | nil) 71 | 72 | (main/when-invoked-as-script 73 | (apply -main *command-line-args*)) 74 | -------------------------------------------------------------------------------- /script/test_shadow_cljs.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns test-shadow-cljs 4 | (:require [helper.jdk :as jdk] 5 | [helper.main :as main] 6 | [helper.shell :as shell] 7 | [lread.status-line :as status])) 8 | 9 | ;; see also: shadow-cljs.edn 10 | (def compiled-tests "target/shadow-cljs/node-test.js") 11 | 12 | (defn -main [& args] 13 | (let [{:keys [version major]} (jdk/version)] 14 | (when (<= major 8) 15 | (status/die 1 "shadow-cljs requires JDK 11 or above, found version %s" version))) 16 | (when (main/doc-arg-opt args) 17 | (status/line :head "testing ClojureScript source with Shadow CLJS, node, optimizations: none") 18 | (shell/command "npx" "shadow-cljs" "compile" "test") 19 | (shell/command "node" compiled-tests)) 20 | nil) 21 | 22 | (main/when-invoked-as-script 23 | (apply -main *command-line-args*)) 24 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:source-paths ["src" "test"] 2 | :dependencies [[org.clojure/test.check "1.1.1"]] 3 | :builds {:test {:target :node-test 4 | :output-to "target/shadow-cljs/node-test.js" 5 | :compiler-options {:warnings 6 | ;; clj-kondo handles deprecation warnings for us 7 | {:fn-deprecated false}}}}} 8 | -------------------------------------------------------------------------------- /src/rewrite_clj.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj 2 | "APIs to navigate and update Clojure/ClojureScript/EDN source code. 3 | 4 | Use [[rewrite-clj.zip]] to ingest your source code into a zipper of nodes and then again to navigate and/or change it. 5 | 6 | Optionally use [[rewrite-clj.parser]] to instead work with raw nodes. 7 | 8 | [[rewrite-clj.node]] will help you to inspect and create nodes. 9 | 10 | [[rewrite-clj.paredit]] first appeared in the ClojureScript only version of rewrite-clj and supports structured editing of the zipper tree.") 11 | -------------------------------------------------------------------------------- /src/rewrite_clj/custom_zipper/switchable.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.custom-zipper.switchable) 2 | 3 | #?(:clj (set! *warn-on-reflection* true)) 4 | 5 | (defn ^:no-doc custom-zipper? 6 | [value] 7 | (:rewrite-clj.custom-zipper.core/custom? value)) 8 | 9 | (defmacro defn-switchable 10 | [sym docstring params & body] 11 | (let [placeholders (repeatedly (count params) gensym) 12 | arglists (list params)] 13 | `(defn ~sym 14 | ~docstring 15 | {:arglists '~arglists} 16 | [~@placeholders] 17 | (if (custom-zipper? ~(first placeholders)) 18 | (let [~@(interleave params placeholders)] 19 | ~@body) 20 | (~(symbol "clojure.zip" (name sym)) ~@placeholders))))) 21 | -------------------------------------------------------------------------------- /src/rewrite_clj/interop.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.interop 2 | #?(:cljs (:require [goog.string :as gstring] 3 | goog.string.format))) 4 | 5 | #?(:clj (set! *warn-on-reflection* true)) 6 | 7 | (defn simple-format 8 | "Interop version of string format 9 | Note that there a big differences between Java's format and Google Closure's format - we don't address them. 10 | %d and %s are known to work in both." 11 | [template & args] 12 | #?(:clj (apply format template args) 13 | :cljs (apply gstring/format template args))) 14 | 15 | (defn str->int 16 | [s] 17 | #?(:clj (Long/parseLong s) 18 | :cljs (js/parseInt s))) 19 | 20 | (defn int->str 21 | [n base] 22 | #?(:clj (.toString (biginteger n) base) 23 | :cljs (.toString n base))) 24 | 25 | (defn min-int[] 26 | #?(:clj Long/MIN_VALUE 27 | :cljs js/Number.MIN_SAFE_INTEGER)) 28 | 29 | (defn max-int[] 30 | #?(:clj Long/MAX_VALUE 31 | :cljs js/Number.MAX_SAFE_INTEGER)) 32 | 33 | (defn clojure-whitespace? 34 | [#?(:clj ^java.lang.Character c :default c)] 35 | #?(:clj (and c (or (= c \,) (Character/isWhitespace c))) 36 | :cljs (and c (< -1 (.indexOf #js [\return \newline \tab \space ","] c))))) 37 | 38 | (defn meta-available? 39 | [data] 40 | #?(:clj (instance? clojure.lang.IMeta data) 41 | :cljs (implements? IWithMeta data))) 42 | 43 | (defn numeric? 44 | "Checks whether a given character is numeric 45 | 46 | Cribbed from clojure/cljs.tools.reader.impl.util." 47 | [^Character ch] 48 | (when ch 49 | #?(:clj (Character/isDigit ch) 50 | :cljs (gstring/isNumeric ch)))) 51 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/comment.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.comment 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defrecord CommentNode [prefix s] 9 | node/Node 10 | (tag [_node] :comment) 11 | (node-type [_node] :comment) 12 | (printable-only? [_node] true) 13 | (sexpr* [_node _opts] 14 | (throw (ex-info "unsupported operation" {}))) 15 | (length [_node] 16 | (+ (count prefix) (count s))) 17 | (string [_node] 18 | (str prefix s)) 19 | 20 | Object 21 | (toString [node] 22 | (node/string node))) 23 | 24 | (node/make-printable! CommentNode) 25 | 26 | ;; ## Constructor 27 | 28 | (defn comment-node 29 | "Create node representing a comment with text `s`. 30 | 31 | You may optionally specify a `prefix` of `\";\"` or `\"#!\"`, defaults is `\";\"`. 32 | 33 | Argument `s`: 34 | - must not include the `prefix` 35 | - usually includes the trailing newline character, otherwise subsequent nodes will be on the comment line 36 | 37 | ```Clojure 38 | (require '[rewrite-clj.node :as n]) 39 | 40 | (-> (n/comment-node \"; my comment\\n\") 41 | n/string) 42 | ;; => \";; my comment\\n\" 43 | 44 | (-> (n/comment-node \"#!\" \"/usr/bin/env bb\\n\") 45 | n/string) 46 | ;; => \"#!/usr/bin/env bb\\n\" 47 | ```" 48 | ([s] 49 | (comment-node ";" s)) 50 | ([prefix s] 51 | {:pre [(and (re-matches #"[^\r\n]*[\r\n]?" s) 52 | (or (= prefix ";") (= prefix "#!")))]} 53 | (->CommentNode prefix s))) 54 | 55 | (defn comment? 56 | "Returns true if `node` is a comment." 57 | [node] 58 | (= (node/tag node) :comment)) 59 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/extras.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.extras 2 | (:require [rewrite-clj.node.comment :as ncomment] 3 | [rewrite-clj.node.whitespace :as nwhitespace])) 4 | 5 | (defn whitespace-or-comment? 6 | "Check whether the given node represents whitespace or comment." 7 | [node] 8 | (or (nwhitespace/whitespace? node) 9 | (ncomment/comment? node))) 10 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/fn.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.fn 2 | (:require [clojure.string :as string] 3 | [clojure.walk :as w] 4 | [rewrite-clj.interop :as interop] 5 | [rewrite-clj.node.protocols :as node])) 6 | 7 | #?(:clj (set! *warn-on-reflection* true)) 8 | 9 | ;; ## Conversion 10 | 11 | (defn- construct-fn 12 | "Construct function form." 13 | [fixed-arg-syms vararg-sym body] 14 | (list 15 | 'fn* 16 | (vec 17 | (concat 18 | fixed-arg-syms 19 | (when vararg-sym 20 | (list '& vararg-sym)))) 21 | body)) 22 | 23 | (defn- arg-index 24 | "Get index based on the substring following the arg's `%`. 25 | Zero means vararg." 26 | [n] 27 | (cond (= n "&") 0 28 | (= n "") 1 29 | (re-matches #"\d+" n) (interop/str->int n) 30 | :else (throw (ex-info "arg literal must be %, %& or %integer." {})))) 31 | 32 | (defn- arg-symbol->gensym 33 | "If symbol starting with `%`, convert to respective gensym." 34 | [gensym-seq vararg? max-fixed-arg-ndx sym] 35 | (when (symbol? sym) 36 | (let [nm (name sym)] 37 | (when (string/starts-with? nm "%") 38 | (let [param-ndx (arg-index (subs nm 1))] 39 | (when (and (= param-ndx 0) (not @vararg?)) 40 | (reset! vararg? true)) 41 | (swap! max-fixed-arg-ndx max param-ndx) 42 | (nth gensym-seq param-ndx)))))) 43 | 44 | (defn- fn-walk 45 | "Walk the form and create an expand function form." 46 | [form] 47 | (let [sym-seq (for [i (range) 48 | :let [base (if (= i 0) 49 | "rest__" 50 | (str "p" i "__")) 51 | s (name (gensym base))]] 52 | (symbol (str s "#"))) 53 | max-fixed-arg-ndx (atom 0) 54 | vararg? (atom false) 55 | body (w/prewalk 56 | #(or (arg-symbol->gensym sym-seq vararg? max-fixed-arg-ndx %) %) 57 | form)] 58 | (construct-fn 59 | (take @max-fixed-arg-ndx (rest sym-seq)) 60 | (when @vararg? 61 | (first sym-seq)) 62 | body))) 63 | 64 | ;; ## Node 65 | 66 | (defrecord FnNode [children] 67 | node/Node 68 | (tag [_node] :fn) 69 | (node-type [_node] :fn) 70 | (printable-only? [_node] false) 71 | (sexpr* [_node opts] 72 | (fn-walk (node/sexprs children opts))) 73 | (length [_node] 74 | (+ 3 (node/sum-lengths children))) 75 | (string [_node] 76 | (str "#(" (node/concat-strings children) ")")) 77 | 78 | node/InnerNode 79 | (inner? [_node] true) 80 | (children [_node] children) 81 | (replace-children [node children'] 82 | (assoc node :children children')) 83 | (leader-length [_node] 2) 84 | 85 | Object 86 | (toString [node] 87 | (node/string node))) 88 | 89 | (node/make-printable! FnNode) 90 | 91 | ;; ## Constructor 92 | 93 | (defn fn-node 94 | "Create node representing an anonymous function with `children`. 95 | 96 | ```Clojure 97 | (require '[rewrite-clj.node :as n]) 98 | 99 | (-> (n/fn-node [(n/token-node '+) 100 | (n/spaces 1) 101 | (n/token-node 1) 102 | (n/spaces 1) 103 | (n/token-node '%1)]) 104 | n/string) 105 | ;; => \"#(+ 1 %1)\" 106 | ```" 107 | [children] 108 | (->FnNode children)) 109 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/forms.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.forms 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defrecord FormsNode [children] 9 | node/Node 10 | (tag [_node] :forms) 11 | (node-type [_node] :forms) 12 | (printable-only? [_node] false) 13 | (sexpr* [_node opts] 14 | (let [es (node/sexprs children opts)] 15 | (if (next es) 16 | (list* 'do es) 17 | (first es)))) 18 | (length [_node] 19 | (node/sum-lengths children)) 20 | (string [_node] 21 | (node/concat-strings children)) 22 | 23 | node/InnerNode 24 | (inner? [_node] true) 25 | (children [_node] children) 26 | (replace-children [this children'] 27 | (assoc this :children children')) 28 | (leader-length [_node] 0) 29 | 30 | Object 31 | (toString [node] 32 | (node/string node))) 33 | 34 | (node/make-printable! FormsNode) 35 | 36 | ;; ## Constructor 37 | 38 | (defn forms-node 39 | "Create top-level node wrapping multiple `children`. 40 | The forms node is equivalent to an implicit `do` at the top-level. 41 | 42 | ```Clojure 43 | (require '[rewrite-clj.node :as n]) 44 | 45 | (-> (n/forms-node [(n/token-node 1) 46 | (n/spaces 1) 47 | (n/token-node 2)]) 48 | n/string) 49 | ;; => \"1 2\" 50 | ``` 51 | " 52 | [children] 53 | (->FormsNode children)) 54 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/integer.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.integer 2 | (:require [rewrite-clj.interop :as interop] 3 | [rewrite-clj.node.protocols :as node])) 4 | 5 | #?(:clj (set! *warn-on-reflection* true)) 6 | 7 | ;; ## Node 8 | 9 | (defrecord IntNode [value base] 10 | node/Node 11 | (tag [_node] :token) 12 | (node-type [_node] :int) 13 | (printable-only? [_node] false) 14 | (sexpr* [_node _opts] value) 15 | (length [node] 16 | (count (node/string node))) 17 | (string [_node] 18 | (let [sign (when (< value 0) 19 | "-") 20 | abs-value (cond-> value (< value 0) -) 21 | s (interop/int->str abs-value base) 22 | prefix (case (long base) 23 | 8 "0" 24 | 10 "" 25 | 16 "0x" 26 | (str base "r"))] 27 | (str sign prefix s))) 28 | 29 | Object 30 | (toString [node] 31 | (node/string node))) 32 | 33 | (node/make-printable! IntNode) 34 | 35 | ;; ## Constructor 36 | 37 | (defn integer-node 38 | "Create node representing an integer `value` in `base`. 39 | 40 | `base` defaults to 10. 41 | 42 | ```Clojure 43 | (require '[rewrite-clj.node :as n]) 44 | 45 | (-> (n/integer-node 42) 46 | n/string) 47 | ;; => \"42\" 48 | 49 | (-> (n/integer-node 31 2) 50 | n/string) 51 | ;; => \"2r11111\" 52 | ``` 53 | 54 | Note: the parser does not currently parse to integer-nodes, but they fully supported for output." 55 | ([value] 56 | (integer-node value 10)) 57 | ([value base] 58 | {:pre [(integer? value) 59 | (integer? base) 60 | (< 1 base 37)]} 61 | (->IntNode value base))) 62 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/keyword.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.keyword 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defn- choose-qualifier [map-qualifier kw-qualifier] 9 | (when (not (and map-qualifier (= "_" (:prefix kw-qualifier)))) 10 | (or kw-qualifier map-qualifier))) 11 | 12 | (defn kw-qualifier [k auto-resolved?] 13 | (when (or auto-resolved? (namespace k)) 14 | {:auto-resolved? auto-resolved? 15 | :prefix (namespace k)})) 16 | 17 | (defn keyword-sexpr [kw kw-auto-resolved? map-qualifier {:keys [auto-resolve]}] 18 | (let [q (choose-qualifier map-qualifier (kw-qualifier kw kw-auto-resolved?))] 19 | (keyword (some-> (if (:auto-resolved? q) 20 | ((or auto-resolve node/default-auto-resolve) 21 | (or (some-> (:prefix q) symbol) 22 | :current)) 23 | (:prefix q)) 24 | str) 25 | (name kw)))) 26 | 27 | (defrecord KeywordNode [k auto-resolved? map-qualifier] 28 | node/Node 29 | (tag [_node] :token) 30 | (node-type [_node] :keyword) 31 | (printable-only? [_node] false) 32 | (sexpr* [_node opts] 33 | (keyword-sexpr k auto-resolved? map-qualifier opts)) 34 | (length [_node] 35 | (let [c (inc (count (name k)))] 36 | (if auto-resolved? 37 | (inc c) 38 | (if-let [nspace (namespace k)] 39 | (+ 1 c (count nspace)) 40 | c)))) 41 | (string [_node] 42 | (str (when auto-resolved? ":") 43 | (pr-str k))) 44 | 45 | node/MapQualifiable 46 | (map-context-apply [node map-qualifier] 47 | (assoc node :map-qualifier map-qualifier)) 48 | (map-context-clear [node] 49 | (assoc node :map-qualifier nil)) 50 | 51 | Object 52 | (toString [node] 53 | (node/string node))) 54 | 55 | (node/make-printable! KeywordNode) 56 | 57 | (defn keyword-node? 58 | "Returns true if `n` is a node representing a keyword." 59 | [n] 60 | (= :keyword (node/node-type n))) 61 | 62 | ;; ## Constructor 63 | 64 | (defn keyword-node 65 | "Create a node representing a keyword `k`. 66 | 67 | Optionally include `auto-resolved?`, which defaults to `false`. 68 | 69 | ```Clojure 70 | (require '[rewrite-clj.node :as n]) 71 | 72 | ;; unqualified keyword 73 | (-> (n/keyword-node :kw) 74 | n/string) 75 | ;; => \":kw\" 76 | 77 | ;; qualified keyword 78 | (-> (n/keyword-node :my-prefix/kw) 79 | n/string) 80 | ;; => \":my-prefix/kw\" 81 | 82 | ;; keyword auto-resolved to current ns 83 | (-> (n/keyword-node :kw true) 84 | n/string) 85 | ;; => \"::kw\" 86 | 87 | ;; keyword auto-resolved to a namespace with given alias 88 | (-> (n/keyword-node :ns-alias/kw true) 89 | n/string) 90 | ;; => \"::ns-alias/kw\" 91 | ```" 92 | ([k auto-resolved?] 93 | {:pre [(keyword? k)]} 94 | (->KeywordNode k auto-resolved? nil)) 95 | ([k] (keyword-node k false))) 96 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/meta.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.meta 2 | (:require [rewrite-clj.interop :as interop] 3 | [rewrite-clj.node.protocols :as node] 4 | [rewrite-clj.node.whitespace :as ws])) 5 | 6 | #?(:clj (set! *warn-on-reflection* true)) 7 | 8 | ;; ## Node 9 | 10 | (defrecord MetaNode [tag prefix children] 11 | node/Node 12 | (tag [_node] tag) 13 | (node-type [_node] :meta) 14 | (printable-only? [_node] false) 15 | (sexpr* [_node opts] 16 | (let [[mta data] (node/sexprs children opts)] 17 | (assert (interop/meta-available? data) 18 | (str "cannot attach metadata to: " (pr-str data))) 19 | (vary-meta data merge 20 | (cond (map? mta) mta 21 | (keyword? mta) {mta true} 22 | (symbol? mta) {:tag mta} 23 | (string? mta) {:tag mta} 24 | (vector? mta) {:param-tags mta} 25 | :else (throw (ex-info "Metadata must be a map, keyword, symbol or string" {})))))) 26 | (length [_node] 27 | (+ (count prefix) (node/sum-lengths children))) 28 | (string [_node] 29 | (str prefix (node/concat-strings children))) 30 | 31 | node/InnerNode 32 | (inner? [_node] true) 33 | (children [_node] children) 34 | (replace-children [this children'] 35 | (node/assert-sexpr-count children' 2) 36 | (assoc this :children children')) 37 | (leader-length [_node] 38 | (count prefix)) 39 | 40 | Object 41 | (toString [node] 42 | (node/string node))) 43 | 44 | (node/make-printable! MetaNode) 45 | 46 | ;; ## Constructor 47 | 48 | (defn meta-node 49 | "Create a node representing a form with metadata. 50 | 51 | When creating manually, you can specify `metadata` and `data` and spacing between the 2 elems will be included: 52 | 53 | ```Clojure 54 | (require '[rewrite-clj.node :as n]) 55 | 56 | (-> (n/meta-node (n/keyword-node :foo) 57 | (n/vector-node [(n/token-node 1)])) 58 | n/string) 59 | ;; => \"^:foo [1]\" 60 | 61 | (-> (n/meta-node (n/map-node [:foo (n/spaces 1) 42]) 62 | (n/vector-node [(n/token-node 1)])) 63 | n/string) 64 | ;; => \"^{:foo 42} [1]\" 65 | ``` 66 | When specifying a sequence of `children`, spacing is explicit: 67 | 68 | ```Clojure 69 | (-> (n/meta-node [(n/keyword-node :foo) 70 | (n/spaces 1) 71 | (n/vector-node [(n/token-node 1)])]) 72 | n/string) 73 | ;; => \"^:foo [1]\" 74 | ``` 75 | See also: [[raw-meta-node]]" 76 | ([children] 77 | (node/assert-sexpr-count children 2) 78 | (->MetaNode :meta "^" children)) 79 | ([metadata data] 80 | (meta-node [metadata (ws/spaces 1) data]))) 81 | 82 | (defn raw-meta-node 83 | "Create a node representing a form with metadata that renders to the reader syntax. 84 | 85 | When creating manually, you can specify `metadata` and `data` and spacing between the 2 elems will be included: 86 | 87 | ```Clojure 88 | (require '[rewrite-clj.node :as n]) 89 | 90 | (-> (n/raw-meta-node (n/keyword-node :foo) 91 | (n/vector-node [(n/token-node 2)])) 92 | n/string) 93 | ;; => \"#^:foo [2]\" 94 | 95 | (-> (n/raw-meta-node (n/map-node [:foo (n/spaces 1) 42]) 96 | (n/vector-node [(n/token-node 2)])) 97 | n/string) 98 | ;; => \"#^{:foo 42} [2]\" 99 | ``` 100 | When specifying a sequence of `children`, spacing is explicit: 101 | 102 | ```Clojure 103 | (require '[rewrite-clj.node :as n]) 104 | 105 | (-> (n/raw-meta-node [(n/keyword-node :foo) 106 | (n/spaces 1) 107 | (n/vector-node [(n/token-node 2)])]) 108 | n/string) 109 | ;; => \"#^:foo [2]\" 110 | ``` 111 | See also: [[meta-node]]" 112 | ([children] 113 | (node/assert-sexpr-count children 2) 114 | (->MetaNode :meta* "#^" children)) 115 | ([metadata data] 116 | (raw-meta-node [metadata (ws/spaces 1) data]))) 117 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/quote.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.quote 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defrecord QuoteNode [tag prefix sym children] 9 | node/Node 10 | (tag [_node] tag) 11 | (node-type [_node] :quote) 12 | (printable-only? [_node] false) 13 | (sexpr* [_node opts] 14 | (list sym (first (node/sexprs children opts)))) 15 | (length [_node] 16 | (+ (count prefix) (node/sum-lengths children))) 17 | (string [_node] 18 | (str prefix (node/concat-strings children))) 19 | 20 | node/InnerNode 21 | (inner? [_node] true) 22 | (children [_node] children) 23 | (replace-children [node children'] 24 | (node/assert-single-sexpr children') 25 | (assoc node :children children')) 26 | (leader-length [_node] 27 | (count prefix)) 28 | 29 | Object 30 | (toString [node] 31 | (node/string node))) 32 | 33 | (node/make-printable! QuoteNode) 34 | 35 | ;; ## Constructors 36 | 37 | (defn- ->node 38 | [t prefix sym children] 39 | (node/assert-single-sexpr children) 40 | (->QuoteNode t prefix sym children)) 41 | 42 | (defn quote-node 43 | "Create node representing a single quoted form where `children` 44 | is either a sequence of nodes or a single node. 45 | 46 | ```Clojure 47 | (require '[rewrite-clj.node :as n]) 48 | 49 | (-> (n/quote-node (n/token-node 'sym)) 50 | (n/string)) 51 | ;; => \"'sym\" 52 | 53 | ;; specifying a sequence allows for whitespace between the 54 | ;; quote and the quoted 55 | (-> (n/quote-node [(n/spaces 10) 56 | (n/token-node 'sym1) ]) 57 | n/string) 58 | ;; => \"' sym1\" 59 | ```" 60 | [children] 61 | (if (sequential? children) 62 | (->node 63 | :quote "'" 'quote 64 | children) 65 | (recur [children]))) 66 | 67 | (defn syntax-quote-node 68 | "Create node representing a single syntax-quoted form where `children` 69 | is either a sequence of nodes or a single node. 70 | 71 | ```Clojure 72 | (require '[rewrite-clj.node :as n]) 73 | 74 | (-> (n/syntax-quote-node (n/token-node 'map)) 75 | n/string) 76 | ;; => \"`map\" 77 | 78 | ;; specifying a sequence allows for whitespace between the 79 | ;; syntax quote and the syntax quoted 80 | (-> (n/syntax-quote-node [(n/spaces 3) 81 | (n/token-node 'map)]) 82 | n/string) 83 | ;; => \"` map\" 84 | ```" 85 | [children] 86 | (if (sequential? children) 87 | (->node 88 | :syntax-quote "`" 'quote 89 | children) 90 | (recur [children]))) 91 | 92 | (defn unquote-node 93 | "Create node representing a single unquoted form where `children` 94 | is either a sequence of nodes or a single node. 95 | 96 | ```Clojure 97 | (require '[rewrite-clj.node :as n]) 98 | 99 | (-> (n/unquote-node (n/token-node 'my-var)) 100 | n/string) 101 | ;; => \"~my-var\" 102 | 103 | ;; specifying a sequence allows for whitespace between the 104 | ;; unquote and the uquoted 105 | (-> (n/unquote-node [(n/spaces 4) 106 | (n/token-node 'my-var)]) 107 | n/string) 108 | ;; => \"~ my-var\" 109 | ```" 110 | [children] 111 | (if (sequential? children) 112 | (->node 113 | :unquote "~" 'clojure.core/unquote 114 | children) 115 | (recur [children]))) 116 | 117 | (defn unquote-splicing-node 118 | "Create node representing a single unquote-spliced form where `children` 119 | is either a sequence of nodes or a single node. 120 | 121 | ```Clojure 122 | (require '[rewrite-clj.node :as n]) 123 | 124 | (-> (n/unquote-splicing-node (n/token-node 'my-var)) 125 | n/string) 126 | ;; => \"~@my-var\" 127 | 128 | ;; specifying a sequence allows for whitespace between the 129 | ;; splicing unquote and the splicing unquoted 130 | (-> (n/unquote-splicing-node [(n/spaces 2) 131 | (n/token-node 'my-var)]) 132 | n/string) 133 | ;; => \"~@ my-var\" 134 | ```" 135 | [children] 136 | (if (sequential? children) 137 | (->node 138 | :unquote-splicing "~@" 'clojure.core/unquote-splicing 139 | children) 140 | (recur [children]))) 141 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/regex.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.regex 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defrecord RegexNode [pattern] 9 | node/Node 10 | (tag [_node] :regex) 11 | (node-type [_node] :regex) 12 | (printable-only? [_node] false) 13 | (sexpr* [_node _opts] 14 | (list 're-pattern pattern)) 15 | (length [_node] 16 | (+ 3 ;; 2 double quotes and a hash 17 | (count pattern))) 18 | (string [_node] 19 | (str "#\"" pattern "\"")) 20 | 21 | Object 22 | (toString [node] 23 | (node/string node))) 24 | 25 | (node/make-printable! RegexNode) 26 | 27 | ;; Internal Utils 28 | 29 | (defn pattern-string-for-regex [#?(:clj ^java.util.regex.Pattern regex 30 | :cljs regex)] 31 | #?(:clj (.pattern regex) 32 | :cljs (.. regex -source))) 33 | 34 | ;; ## Constructor 35 | 36 | (defn regex-node 37 | "Create node representing a regex with `pattern-string`. 38 | Use same escape rules for `pattern-string` as you would for `(re-pattern \"pattern-string\")` 39 | 40 | ```Clojure 41 | (require '[rewrite-clj.node :as n]) 42 | 43 | (-> (n/regex-node \"my\\\\.lil.*regex\") 44 | n/string) 45 | ;; => \"#\\\"my\\\\.lil.*regex\\\"\" 46 | ```" 47 | [pattern-string] 48 | (->RegexNode pattern-string)) 49 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/string.clj: -------------------------------------------------------------------------------- 1 | ;; DO NOT EDIT FILE, automatically generated from: ./template/rewrite_clj/node/string.clj 2 | (ns ^:no-doc rewrite-clj.node.string 3 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 4 | This ns does not work for cljs due to namespace collisions." 5 | (:require [rewrite-clj.node.stringz])) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | 10 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.node.stringz 11 | (defn string-node 12 | "Create node representing a string value where `lines` can be a sequence of strings or a single string. 13 | 14 | When `lines` is a sequence, the resulting node `tag` will be `:multi-line`, otherwise `:token`. 15 | 16 | `:multi-line` refers to a single string in your source that appears over multiple lines, for example: 17 | 18 | ```Clojure 19 | (def s \"foo 20 | bar 21 | baz\") 22 | ``` 23 | 24 | It does not apply to a string that appears on a single line that includes escaped newlines, for example: 25 | 26 | ```Clojure 27 | (def s \"foo\\nbar\\n\\baz\") 28 | ``` 29 | 30 | Naive examples (see example on escaping below): 31 | 32 | ```Clojure 33 | (require '[rewrite-clj.node :as n]) 34 | 35 | (-> (n/string-node \"hello\") 36 | n/string) 37 | ;; => \"\\\"hello\\\"\" 38 | 39 | (-> (n/string-node [\"line1\" \"\" \"line3\"]) 40 | n/string) 41 | ;; => \"\\\"line1\\n\\nline3\\\"\" 42 | ``` 43 | 44 | This function was originally written to serve the rewrite-clj parser. 45 | Escaping and wrapping expectations are non-obvious. 46 | - characters within strings are assumed to be escaped 47 | - but the string should not wrapped with `\\\"` 48 | 49 | Here's an example of conforming to these expectations for a string that has escape sequences. 50 | (Best to view this on cljdoc, docstring string escaping is confusing). 51 | 52 | ```Clojure 53 | (require '[clojure.string :as string]) 54 | 55 | (defn pr-str-unwrapped [s] 56 | (apply str (-> s pr-str next butlast))) 57 | 58 | (-> \"hey \\\" man\" 59 | pr-str-unwrapped 60 | n/string-node 61 | n/string) 62 | ;; => \"\\\"hey \\\\\\\" man\\\"\" 63 | ``` 64 | 65 | To construct strings appearing on a single line, consider [[token-node]]. 66 | It will handle escaping for you." 67 | [lines] (rewrite-clj.node.stringz/string-node lines)) 68 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/stringz.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.stringz 2 | (:require [clojure.string :as string] 3 | [clojure.tools.reader.edn :as edn] 4 | [rewrite-clj.node.protocols :as node] )) 5 | 6 | #?(:clj (set! *warn-on-reflection* true)) 7 | 8 | ;; ## Node 9 | 10 | (defn- wrap-string [s] 11 | (str "\"" s "\"")) 12 | 13 | (defn- join-lines [lines] 14 | (string/join "\n" lines)) 15 | 16 | (defrecord StringNode [lines] 17 | node/Node 18 | (tag [_node] 19 | (if (next lines) 20 | :multi-line 21 | :token)) 22 | (node-type [_node] :string) 23 | (printable-only? [_node] false) 24 | (sexpr* [_node _opts] 25 | (join-lines 26 | (map 27 | (comp edn/read-string wrap-string) 28 | lines))) 29 | (length [_node] 30 | (+ 2 (reduce + (map count lines)))) 31 | (string [_node] 32 | (wrap-string (join-lines lines))) 33 | 34 | Object 35 | (toString [node] 36 | (node/string node))) 37 | 38 | (node/make-printable! StringNode) 39 | 40 | ;; ## Constructors 41 | 42 | (defn string-node 43 | "Create node representing a string value where `lines` can be a sequence of strings or a single string. 44 | 45 | When `lines` is a sequence, the resulting node `tag` will be `:multi-line`, otherwise `:token`. 46 | 47 | `:multi-line` refers to a single string in your source that appears over multiple lines, for example: 48 | 49 | ```Clojure 50 | (def s \"foo 51 | bar 52 | baz\") 53 | ``` 54 | 55 | It does not apply to a string that appears on a single line that includes escaped newlines, for example: 56 | 57 | ```Clojure 58 | (def s \"foo\\nbar\\n\\baz\") 59 | ``` 60 | 61 | Naive examples (see example on escaping below): 62 | 63 | ```Clojure 64 | (require '[rewrite-clj.node :as n]) 65 | 66 | (-> (n/string-node \"hello\") 67 | n/string) 68 | ;; => \"\\\"hello\\\"\" 69 | 70 | (-> (n/string-node [\"line1\" \"\" \"line3\"]) 71 | n/string) 72 | ;; => \"\\\"line1\\n\\nline3\\\"\" 73 | ``` 74 | 75 | This function was originally written to serve the rewrite-clj parser. 76 | Escaping and wrapping expectations are non-obvious. 77 | - characters within strings are assumed to be escaped 78 | - but the string should not wrapped with `\\\"` 79 | 80 | Here's an example of conforming to these expectations for a string that has escape sequences. 81 | (Best to view this on cljdoc, docstring string escaping is confusing). 82 | 83 | ```Clojure 84 | (require '[clojure.string :as string]) 85 | 86 | (defn pr-str-unwrapped [s] 87 | (apply str (-> s pr-str next butlast))) 88 | 89 | (-> \"hey \\\" man\" 90 | pr-str-unwrapped 91 | n/string-node 92 | n/string) 93 | ;; => \"\\\"hey \\\\\\\" man\\\"\" 94 | ``` 95 | 96 | To construct strings appearing on a single line, consider [[token-node]]. 97 | It will handle escaping for you." 98 | [lines] 99 | (if (string? lines) 100 | (->StringNode [lines]) 101 | (->StringNode lines))) 102 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/token.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.token 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defn- choose-qualifier [map-qualifier sym-qualifier] 9 | (when (not (and map-qualifier (= "_" (:prefix sym-qualifier)))) 10 | (or sym-qualifier map-qualifier))) 11 | 12 | (defn- symbol-qualifier [value] 13 | (when (and (symbol? value) (namespace value)) 14 | {:prefix (namespace value)})) 15 | 16 | ;; A symbol is different than a keyword in that it can only be auto-resolve qualified by a namespaced map 17 | (defn- symbol-sexpr [value map-qualifier {:keys [auto-resolve]}] 18 | (let [q (choose-qualifier map-qualifier (symbol-qualifier value))] 19 | (symbol (some-> (if (:auto-resolved? q) 20 | ((or auto-resolve node/default-auto-resolve) 21 | (or (some-> (:prefix q) symbol) 22 | :current)) 23 | (:prefix q)) 24 | str) 25 | (name value)))) 26 | 27 | (defrecord TokenNode [value string-value] 28 | node/Node 29 | (tag [_ndoe] :token) 30 | (node-type [_node] :token) 31 | (printable-only? [_node] false) 32 | (sexpr* [_node _opts] value) 33 | (length [_node] 34 | (count string-value)) 35 | (string [_node] string-value) 36 | 37 | Object 38 | (toString [node] 39 | (node/string node))) 40 | 41 | (defrecord SymbolNode [value string-value map-qualifier] 42 | node/Node 43 | (tag [_node] :token) 44 | (node-type [_node] :symbol) 45 | (printable-only? [_node] false) 46 | (sexpr* [_node opts] 47 | (symbol-sexpr value map-qualifier opts)) 48 | (length [_node] (count string-value)) 49 | (string [_node] string-value) 50 | 51 | node/MapQualifiable 52 | (map-context-apply [node map-qualifier] 53 | (assoc node :map-qualifier map-qualifier)) 54 | (map-context-clear [node] 55 | (assoc node :map-qualifier nil)) 56 | 57 | Object 58 | (toString [node] 59 | (node/string node))) 60 | 61 | (node/make-printable! TokenNode) 62 | (node/make-printable! SymbolNode) 63 | 64 | (defn symbol-node? 65 | "Returns true if `n` is a node representing a symbol." 66 | [n] 67 | (= :symbol (node/node-type n))) 68 | 69 | ;; ## Constructor 70 | 71 | (defn token-node 72 | "Create node for an unspecified token of `value`. 73 | 74 | ```Clojure 75 | (require '[rewrite-clj.node :as n]) 76 | 77 | (-> (n/token-node 'sym) n/string) 78 | ;; => \"sym\" 79 | 80 | (-> (n/token-node 42) n/string) 81 | ;; => \"42\" 82 | 83 | (-> (n/token-node \"astring\") n/string) 84 | ;; => \"\\\"astring\\\"\" 85 | ``` 86 | 87 | To construct strings appearing over multiple lines, see [[string-node]]." 88 | ([value] 89 | (token-node value (pr-str value))) 90 | ([value string-value] 91 | (if (symbol? value) 92 | (->SymbolNode value string-value nil) 93 | (->TokenNode value string-value)))) 94 | -------------------------------------------------------------------------------- /src/rewrite_clj/node/uneval.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.uneval 2 | (:require [rewrite-clj.node.protocols :as node])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | ;; ## Node 7 | 8 | (defrecord UnevalNode [children] 9 | node/Node 10 | (tag [_node] :uneval) 11 | (node-type [_node] :uneval) 12 | (printable-only? [_node] true) 13 | (sexpr* [_node _opts] 14 | (throw (ex-info "unsupported operation" {}))) 15 | (length [_node] 16 | (+ 2 (node/sum-lengths children))) 17 | (string [_node] 18 | (str "#_" (node/concat-strings children))) 19 | 20 | node/InnerNode 21 | (inner? [_node] true) 22 | (children [_node] children) 23 | (replace-children [node children'] 24 | (node/assert-single-sexpr children') 25 | (assoc node :children children')) 26 | (leader-length [_node] 2) 27 | 28 | Object 29 | (toString [node] 30 | (node/string node))) 31 | 32 | (node/make-printable! UnevalNode) 33 | 34 | ;; ## Constructor 35 | 36 | (defn uneval-node 37 | "Create node representing an unevaled form with `children`. 38 | 39 | ```Clojure 40 | (require '[rewrite-clj.node :as n]) 41 | 42 | (-> (n/uneval-node [(n/spaces 1) 43 | (n/token-node 42)]) 44 | n/string) 45 | ;; => \"#_ 42\" 46 | ```" 47 | [children] 48 | (if (sequential? children) 49 | (do 50 | (node/assert-single-sexpr children) 51 | (->UnevalNode children)) 52 | (recur [children]))) 53 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.parser 2 | "Parse Clojure/ClojureScript/EDN source code to nodes. 3 | 4 | Parsing includes all source code elements including whitespace. 5 | 6 | After parsing, the typical next step is [[rewrite-clj.zip/edn]] to create zipper. 7 | 8 | Alternatively consider parsing and zipping in one step from [[rewrite-clj.zip/of-string]] or [[rewrite-clj.zip/of-file]]." 9 | (:require [rewrite-clj.node.forms :as nforms] 10 | [rewrite-clj.parser.core :as p] 11 | [rewrite-clj.reader :as reader])) 12 | 13 | #?(:clj (set! *warn-on-reflection* true)) 14 | 15 | ;; ## Parser Core 16 | 17 | (defn ^:no-doc parse 18 | "Parse next form from the given reader." 19 | [#?(:cljs ^not-native reader :default reader)] 20 | (p/parse-next reader)) 21 | 22 | (defn ^:no-doc parse-all 23 | "Parse all forms from the given reader." 24 | [#?(:cljs ^not-native reader :default reader)] 25 | (let [nodes (->> (repeatedly #(parse reader)) 26 | (take-while identity)) 27 | position-meta (merge (meta (first nodes)) 28 | (select-keys (meta (last nodes)) 29 | [:end-row :end-col]))] 30 | (with-meta (nforms/forms-node nodes) position-meta))) 31 | 32 | ;; ## Specialized Parsers 33 | 34 | (defn parse-string 35 | "Return a node for first source code element in string `s`." 36 | [s] 37 | (parse (reader/string-reader s))) 38 | 39 | (defn parse-string-all 40 | "Return forms node for all source code elements in string `s`." 41 | [s] 42 | (parse-all (reader/string-reader s))) 43 | 44 | #?(:clj 45 | (defn parse-file 46 | "Return node for first source code element in file `f`." 47 | [f] 48 | (with-open [r (reader/file-reader f)] 49 | (parse r)))) 50 | 51 | #?(:clj 52 | (defn parse-file-all 53 | "Return forms node for all source code elements in file `f`." 54 | [f] 55 | (with-open [r (reader/file-reader f)] 56 | (parse-all r)))) 57 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser/impl.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.parser.impl 2 | {:no-doc true} 3 | (:require [rewrite-clj.reader :as reader]) 4 | #?(:cljs (:import [goog.string StringBuffer]))) 5 | 6 | #?(:clj (set! *warn-on-reflection* true)) 7 | 8 | (defn flush-into 9 | "INTERNAL. Flush buffer and add string to the given vector." 10 | [lines ^StringBuffer buf] 11 | (let [s (.toString buf)] 12 | #?(:clj (.setLength buf 0) :cljs (.clear buf)) 13 | (conj lines s))) 14 | 15 | (defn read-string-data 16 | "INTERNAL." 17 | [#?(:cljs ^not-native reader :default reader)] 18 | (reader/ignore reader) 19 | (let [buf (StringBuffer.)] 20 | (loop [escape? false 21 | lines []] 22 | (if-let [c (reader/next reader)] 23 | (cond (and (not escape?) (identical? c \")) 24 | (flush-into lines buf) 25 | 26 | (identical? c \newline) 27 | (recur escape? (flush-into lines buf)) 28 | 29 | :else 30 | (do 31 | (.append buf c) 32 | (recur (and (not escape?) (identical? c \\)) lines))) 33 | (reader/throw-reader reader "Unexpected EOF while reading string."))))) 34 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser/keyword.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.parser.keyword 2 | (:require [rewrite-clj.node.keyword :as nkeyword] 3 | [rewrite-clj.reader :as reader])) 4 | 5 | #?(:clj (set! *warn-on-reflection* true)) 6 | 7 | (defn parse-keyword 8 | [#?(:cljs ^not-native reader :default reader)] 9 | (reader/ignore reader) 10 | (if-let [c (reader/peek reader)] 11 | (if (= c \:) 12 | (do 13 | (reader/next reader) 14 | (nkeyword/keyword-node 15 | (reader/read-keyword reader) 16 | true)) 17 | (nkeyword/keyword-node (reader/read-keyword reader))) 18 | (reader/throw-reader reader "unexpected EOF while reading keyword."))) 19 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser/namespaced_map.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.parser.namespaced-map 2 | (:require [rewrite-clj.node.namespaced-map :as nsmap] 3 | [rewrite-clj.node.protocols :as node] 4 | [rewrite-clj.node.whitespace :as nws] 5 | [rewrite-clj.reader :as reader] )) 6 | 7 | #?(:clj (set! *warn-on-reflection* true)) 8 | 9 | (defn- parse-qualifier 10 | [reader] 11 | (let [auto-resolved? (= ":" (reader/read-while reader (fn [c] (= \: c)))) 12 | prefix (reader/read-until reader (fn [c] (or (reader/boundary? c) 13 | (reader/whitespace? c))))] 14 | (nsmap/map-qualifier-node auto-resolved? 15 | (when (seq prefix) prefix)))) 16 | 17 | (defn- parse-to-next-elem [reader read-next] 18 | (loop [nodes []] 19 | (let [n (read-next reader)] 20 | (if (and n (nws/whitespace? n)) 21 | (recur (conj nodes n)) 22 | [nodes n])))) 23 | 24 | (defn parse-namespaced-map 25 | "The caller has parsed up to `#:` and delegates the details to us." 26 | [reader read-next] 27 | (reader/ignore reader) 28 | (let [qualifier-node (parse-qualifier reader)] 29 | (when (and (not (:auto-resolved? qualifier-node)) 30 | (nil? (:prefix qualifier-node))) 31 | (reader/throw-reader reader "namespaced map expects a namespace")) 32 | (let [[whitespace-nodes map-node] (parse-to-next-elem reader read-next)] 33 | (when (or (not map-node) 34 | (not= :map (node/tag map-node))) 35 | (reader/throw-reader reader "namespaced map expects a map")) 36 | (nsmap/namespaced-map-node 37 | (concat [qualifier-node] whitespace-nodes [map-node]))))) 38 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser/string.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.parser.string 2 | (:require [clojure.string :as string] 3 | [rewrite-clj.node.stringz :as nstring] 4 | [rewrite-clj.parser.impl :as pimpl])) 5 | 6 | #?(:clj (set! *warn-on-reflection* true)) 7 | 8 | (defn parse-string 9 | [#?(:cljs ^not-native reader :default reader)] 10 | (nstring/string-node (pimpl/read-string-data reader))) 11 | 12 | (defn parse-regex 13 | [#?(:cljs ^not-native reader :default reader)] 14 | (let [h (pimpl/read-string-data reader)] 15 | (string/join "\n" h))) 16 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser/token.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.parser.token 2 | (:require [rewrite-clj.interop :as interop] 3 | [rewrite-clj.node.token :as ntoken] 4 | [rewrite-clj.reader :as r])) 5 | 6 | #?(:clj (set! *warn-on-reflection* true)) 7 | 8 | (defn- read-to-boundary 9 | [#?(:cljs ^not-native reader :default reader) & [allowed]] 10 | (let [allowed? (set allowed)] 11 | (r/read-until 12 | reader 13 | #(and (not (allowed? %)) 14 | (r/whitespace-or-boundary? %))))) 15 | 16 | (defn- read-to-char-boundary 17 | [#?(:cljs ^not-native reader :default reader)] 18 | (let [c (r/next reader)] 19 | (str c 20 | (if (not= c \\) 21 | (read-to-boundary reader) 22 | "")))) 23 | 24 | (defn- symbol-node 25 | "Symbols allow for certain boundary characters that have 26 | to be handled explicitly." 27 | [#?(:cljs ^not-native reader :default reader) value value-string] 28 | (let [suffix (read-to-boundary 29 | reader 30 | [\' \:])] 31 | (if (empty? suffix) 32 | (ntoken/token-node value value-string) 33 | (let [s (str value-string suffix)] 34 | (ntoken/token-node 35 | (r/read-symbol s) 36 | s))))) 37 | 38 | (defn- number-literal? 39 | "Checks whether the reader is at the start of a number literal 40 | 41 | Cribbed and adapted from clojure.tools.reader.impl.commons" 42 | [[c1 c2]] 43 | (or (interop/numeric? c1) 44 | (and (or (identical? \+ c1) (identical? \- c1)) 45 | (interop/numeric? c2)))) 46 | 47 | (defn parse-token 48 | "Parse a single token. For example: symbol, number or character." 49 | [#?(:cljs ^not-native reader :default reader)] 50 | (let [first-char (r/next reader) 51 | s (->> (if (= first-char \\) 52 | (read-to-char-boundary reader) 53 | (read-to-boundary reader)) 54 | (str first-char)) 55 | v (if (or (= first-char \\) ;; character like \n or \newline 56 | (= first-char \#) ;; something like ##Inf, ##Nan 57 | (number-literal? s)) 58 | (r/string->edn s) 59 | (r/read-symbol s))] 60 | (if (symbol? v) 61 | (symbol-node reader v s) 62 | (ntoken/token-node v s)))) 63 | 64 | (comment 65 | (require '[clojure.tools.reader.reader-types :as rt]) 66 | 67 | (parse-token (rt/string-push-back-reader "foo")) 68 | ;; => {:value foo, :string-value "foo", :map-qualifier nil} 69 | 70 | (parse-token (rt/string-push-back-reader "42")) 71 | ;; => {:value 42, :string-value "42"} 72 | 73 | (parse-token (rt/string-push-back-reader "+42")) 74 | ;; => {:value 42, :string-value "+42"} 75 | 76 | (parse-token (rt/string-push-back-reader "\\newline")) 77 | ;; => {:value \newline, :string-value "\\newline"} 78 | 79 | (parse-token (rt/string-push-back-reader "##Inf")) 80 | ;; => {:value ##Inf, :string-value "##Inf"} 81 | 82 | :eoc) 83 | -------------------------------------------------------------------------------- /src/rewrite_clj/parser/whitespace.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.parser.whitespace 2 | (:require [rewrite-clj.node.whitespace :as nwhitespace] 3 | [rewrite-clj.reader :as reader])) 4 | 5 | #?(:clj (set! *warn-on-reflection* true)) 6 | 7 | (defn parse-whitespace 8 | "Parse as much whitespace as possible. The created node can either contain 9 | only linebreaks or only space/tabs." 10 | [#?(:cljs ^not-native reader :default reader)] 11 | (let [c (reader/peek reader)] 12 | (cond (reader/linebreak? c) 13 | (nwhitespace/newline-node 14 | (reader/read-while reader reader/linebreak?)) 15 | 16 | (reader/comma? c) 17 | (nwhitespace/comma-node 18 | (reader/read-while reader reader/comma?)) 19 | 20 | :else 21 | (nwhitespace/whitespace-node 22 | (reader/read-while reader reader/space?))))) 23 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/context.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.context 2 | (:require [rewrite-clj.custom-zipper.core :as zraw] 3 | [rewrite-clj.node.protocols :as protocols] 4 | [rewrite-clj.zip.seqz :as seqz] 5 | [rewrite-clj.zip.walk :as walk])) 6 | 7 | (defn- is-map-key? [zloc] 8 | (->> (iterate zraw/left zloc) 9 | (take-while identity) 10 | count 11 | odd?)) 12 | 13 | (defn reapply-context 14 | "Returns `zloc` with namespaced map sexpr context to all symbols and keywords reapplied from current location downward. 15 | 16 | Keywords and symbols: 17 | * that are keys in a namespaced map will have namespaced map context applied 18 | * otherwise will have any namespaced map context removed 19 | 20 | You should only need to use this function if: 21 | * you care about `sexpr` on keywords and symbols 22 | * and you are moving keywords and symbols from a namespaced map to some other location." 23 | [zloc] 24 | (walk/postwalk zloc 25 | #(satisfies? protocols/MapQualifiable (zraw/node %)) 26 | (fn [zloc] 27 | (let [parent (-> zloc zraw/up zraw/up) 28 | nsmap (when (and parent (seqz/namespaced-map? parent)) parent)] 29 | (if (and nsmap (is-map-key? zloc)) 30 | (zraw/replace zloc (protocols/map-context-apply (zraw/node zloc) (first (protocols/children (zraw/node nsmap))))) 31 | (zraw/replace zloc (protocols/map-context-clear (zraw/node zloc)))))))) 32 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/edit.clj: -------------------------------------------------------------------------------- 1 | ;; DO NOT EDIT FILE, automatically generated from: ./template/rewrite_clj/zip/edit.clj 2 | (ns ^:no-doc rewrite-clj.zip.edit 3 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 4 | This ns does not work for cljs due to namespace collisions." 5 | (:refer-clojure :exclude [replace]) 6 | (:require [rewrite-clj.zip.editz])) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | 11 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.editz 12 | (defn replace 13 | "Return `zloc` with the current node replaced by `item`. 14 | If `item` is not already a node, an attempt will be made to coerce it to one. 15 | 16 | Use [[replace*]] for non-coercing version of replace." 17 | [zloc item] (rewrite-clj.zip.editz/replace zloc item)) 18 | 19 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.editz 20 | (defn edit 21 | "Return `zloc` with the current node replaced with the result of: 22 | 23 | `(apply f (s-expr current-node) args)` 24 | 25 | The result of `f`, if not already a node, will be coerced to a node if possible. 26 | 27 | See docs for [sexpr nuances](/doc/01-user-guide.adoc#sexpr-nuances). 28 | 29 | Use [[edit*]] for non-coercing version of edit." 30 | [zloc f & args] (apply rewrite-clj.zip.editz/edit zloc f args)) 31 | 32 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.editz 33 | (defn splice 34 | "Return zipper with the children of the current node in `zloc` merged into itself. 35 | (akin to Clojure's `unquote-splicing` macro: `~@...`). 36 | - if the node is not one that can have children, no modification will 37 | be performed. 38 | - if the node has no or only whitespace children, it will be removed. 39 | - otherwise, splicing will be performed, moving the zipper to the first 40 | non-whitespace spliced child node. 41 | 42 | For example, given `[[1 2 3] 4 5 6]`, if zloc is located at vector `[1 2 3]`, a splice will result in raising the vector's children up `[1 2 3 4 5 6]` and locating the zipper at node `1`." 43 | [zloc] (rewrite-clj.zip.editz/splice zloc)) 44 | 45 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.editz 46 | (defn prefix 47 | "Return zipper with the current node in `zloc` prefixed with string `s`. 48 | Operates on token node or a multi-line node, else exception is thrown. 49 | When multi-line, first line is prefixed." 50 | [zloc s] (rewrite-clj.zip.editz/prefix zloc s)) 51 | 52 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.editz 53 | (defn suffix 54 | "Return zipper with the current node in `zloc` suffixed with string `s`. 55 | Operates on token node or a multi-line node, else exception is thrown. 56 | When multi-line, last line is suffixed." 57 | [zloc s] (rewrite-clj.zip.editz/suffix zloc s)) 58 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/insert.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.insert 2 | (:require [rewrite-clj.custom-zipper.core :as zraw] 3 | [rewrite-clj.node.protocols :as node] 4 | [rewrite-clj.node.whitespace :as nwhitespace] 5 | [rewrite-clj.zip.whitespace :as zwhitespace])) 6 | 7 | #?(:clj (set! *warn-on-reflection* true)) 8 | 9 | (def ^:private space 10 | (nwhitespace/spaces 1)) 11 | 12 | (defn- ends-with-ws? [zloc] 13 | (when zloc 14 | (or (zwhitespace/whitespace? zloc) 15 | ;; a comment is not necessarily newline terminated, but we don't special case that 16 | ;; inserting after a non-newline termnated comment is caveated in rewrite-clj.node/comment-node 17 | (zwhitespace/comment? zloc)))) 18 | 19 | (defn- starts-with-ws? [zloc] 20 | (zwhitespace/whitespace? zloc)) 21 | 22 | (defn insert-right 23 | "Return `zloc` with `item` inserted to the right of the current node in `zloc`, without moving location. 24 | If `item` is not already a node, an attempt will be made to coerce it to one. 25 | 26 | Will insert spaces around `item` if necessary. 27 | There is no consideration on whitespaceness of `item` itself. 28 | 29 | Use [[rewrite-clj.zip/insert-right*]] to insert without adding any whitespace." 30 | [zloc item] 31 | (let [next-zloc (zraw/right zloc) 32 | item-node (node/coerce item)] 33 | (cond-> zloc 34 | (and next-zloc (not (starts-with-ws? next-zloc))) 35 | (zraw/insert-right space) 36 | 37 | :always 38 | (zraw/insert-right item-node) 39 | 40 | (not (ends-with-ws? zloc)) 41 | (zraw/insert-right space)))) 42 | 43 | (defn insert-left 44 | "Return zipper with `item` inserted to the left of the current node in `zloc`, without moving location. 45 | If `item` is not already a node, an attempt will be made to coerce it to one. 46 | 47 | Will insert spaces around `item` if necessary. 48 | There is no consideration on whitespaceness of `item` itself. 49 | 50 | Use [[insert-left*]] to insert without adding any whitespace." 51 | [zloc item] 52 | (let [prev-zloc (zraw/left zloc) 53 | item-node (node/coerce item)] 54 | (cond-> zloc 55 | (and prev-zloc (not (ends-with-ws? prev-zloc))) 56 | (zraw/insert-left space) 57 | 58 | :always 59 | (zraw/insert-left item-node) 60 | 61 | (not (starts-with-ws? zloc)) 62 | (zraw/insert-left space)))) 63 | 64 | (defn insert-child 65 | "Return `zloc` with `item` inserted as the first child of the current node in `zloc`, without moving location. 66 | If `item` is not already a node, an attempt will be made to coerce it to one. 67 | 68 | Will insert space after `item` if necessary. 69 | There is no consideration on whitespaceness of `item` itself. 70 | 71 | Use [[insert-child*]] to insert without adding any whitespace." 72 | [zloc item] 73 | (let [prev-zloc (zraw/down zloc) 74 | item-node (node/coerce item)] 75 | (cond-> zloc 76 | (and prev-zloc (not (starts-with-ws? prev-zloc))) 77 | (zraw/insert-child space) 78 | 79 | :always 80 | (zraw/insert-child item-node)))) 81 | 82 | (defn append-child 83 | "Return `zloc` with `item` inserted as the last child of the current node in `zloc`, without moving location. 84 | If `item` is not already a node, an attempt will be made to coerce it to one. 85 | 86 | Will insert space before `item` if necessary. 87 | There is no consideration on whitespaceness of `item` itself. 88 | 89 | Use [[append-child*]] to append without adding any whitespace." 90 | [zloc item] 91 | (let [prev-zloc (some-> zloc zraw/down zraw/rightmost) 92 | item-node (node/coerce item)] 93 | (cond-> zloc 94 | (and prev-zloc (not (ends-with-ws? prev-zloc))) 95 | (zraw/append-child space) 96 | 97 | :always 98 | (zraw/append-child item-node)))) 99 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/move.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.move 2 | (:refer-clojure :exclude [next]) 3 | (:require [rewrite-clj.custom-zipper.core :as zraw] 4 | [rewrite-clj.zip.whitespace :as ws])) 5 | 6 | #?(:clj (set! *warn-on-reflection* true)) 7 | 8 | (defn right 9 | "Return zipper with location moved right to next non-whitespace/non-comment sibling of current node in `zloc`." 10 | [zloc] 11 | (some-> zloc zraw/right ws/skip-whitespace)) 12 | 13 | (defn left 14 | "Return zipper with location moved left to next non-whitespace/non-comment sibling of current node in `zloc`." 15 | [zloc] 16 | (some-> zloc zraw/left ws/skip-whitespace-left)) 17 | 18 | (defn down 19 | "Return zipper with location moved down to the first non-whitespace/non-comment child node of the current node in `zloc`, or nil if no applicable children." 20 | [zloc] 21 | (some-> zloc zraw/down ws/skip-whitespace)) 22 | 23 | (defn up 24 | "Return zipper with location moved up to next non-whitespace/non-comment parent of current node in `zloc`, or `nil` if at the top." 25 | [zloc] 26 | (some-> zloc zraw/up ws/skip-whitespace-left)) 27 | 28 | (defn next 29 | "Return zipper with location moved to the next depth-first non-whitespace/non-comment node in `zloc`. 30 | End can be detected with [[end?]], if already at end, stays there." 31 | [zloc] 32 | (when zloc 33 | (or (some->> zloc 34 | zraw/next 35 | (ws/skip-whitespace zraw/next)) 36 | (vary-meta zloc assoc ::end? true)))) 37 | 38 | (defn end? 39 | "Return true if `zloc` is at end of depth-first traversal." 40 | [zloc] 41 | (or (not zloc) 42 | (zraw/end? zloc) 43 | (::end? (meta zloc)))) 44 | 45 | (defn rightmost? 46 | "Return true if at rightmost non-whitespace/non-comment sibling node in `zloc`." 47 | [zloc] 48 | (nil? (ws/skip-whitespace (zraw/right zloc)))) 49 | 50 | (defn leftmost? 51 | "Return true if at leftmost non-whitespace/non-comment sibling node in `zloc`." 52 | [zloc] 53 | (nil? (ws/skip-whitespace-left (zraw/left zloc)))) 54 | 55 | (defn prev 56 | "Return zipper with location moved to the previous depth-first non-whitespace/non-comment node in `zloc`. If already at root, returns nil." 57 | [zloc] 58 | (some->> zloc 59 | zraw/prev 60 | (ws/skip-whitespace zraw/prev))) 61 | 62 | (defn leftmost 63 | "Return zipper with location moved to the leftmost non-whitespace/non-comment sibling of current node in `zloc`." 64 | [zloc] 65 | (some-> zloc 66 | zraw/leftmost 67 | ws/skip-whitespace)) 68 | 69 | (defn rightmost 70 | "Return zipper with location moved to the rightmost non-whitespace/non-comment sibling of current node in `zloc`." 71 | [zloc] 72 | (some-> zloc 73 | zraw/rightmost 74 | ws/skip-whitespace-left)) 75 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/options.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.options 2 | (:require [rewrite-clj.node.protocols :as protocols])) 3 | 4 | #?(:clj (set! *warn-on-reflection* true)) 5 | 6 | (def default-zipper-opts 7 | {:track-position? false 8 | :auto-resolve protocols/default-auto-resolve}) 9 | 10 | (defn get-opts [zloc] 11 | (:rewrite-clj.zip/opts (meta zloc))) 12 | 13 | (defn set-opts [zloc opts] 14 | (vary-meta zloc assoc :rewrite-clj.zip/opts (merge default-zipper-opts opts))) 15 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/remove.clj: -------------------------------------------------------------------------------- 1 | ;; DO NOT EDIT FILE, automatically generated from: ./template/rewrite_clj/zip/remove.clj 2 | (ns ^:no-doc rewrite-clj.zip.remove 3 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 4 | This ns does not work for cljs due to namespace collisions." 5 | (:refer-clojure :exclude [remove]) 6 | (:require [rewrite-clj.zip.removez])) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | 11 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.removez 12 | (defn remove 13 | "Return `zloc` with current node removed. Returned zipper location 14 | is moved to the first non-whitespace node preceding removed node in a depth-first walk. 15 | Removes whitespace appropriately. 16 | 17 | - `[1 |2 3] => [|1 3]` 18 | - `[1 |2] => [|1]` 19 | - `[|1 2] => |[2]` 20 | - `[|1] => |[]` 21 | - `[ |1 ] => |[]` 22 | - `[1 [2 3] |4] => [1 [2 |3]]` 23 | - `[|1 [2 3] 4] => |[[2 3] 4]` 24 | 25 | If the removed node is a rightmost sibling, both leading and trailing whitespace 26 | is removed, otherwise only trailing whitespace is removed. 27 | 28 | The result is that a following element (no matter whether it is on the same line 29 | or not) will end up at same positon (line/column) as the removed one. 30 | If a comment lies betwen the original node and the neighbour this will not hold true. 31 | 32 | If the removed node is at end of input and is trailed by 1 or more newlines, 33 | a single trailing newline will be preserved. 34 | 35 | Use [[remove*]] to remove node without removing any surrounding whitespace." 36 | [zloc] (rewrite-clj.zip.removez/remove zloc)) 37 | 38 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.removez 39 | (defn remove-preserve-newline 40 | "Same as [[remove]] but preserves newlines. 41 | Specifically: will trim all whitespace - or whitespace up to first linebreak if present." 42 | [zloc] (rewrite-clj.zip.removez/remove-preserve-newline zloc)) 43 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/removez.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.removez 2 | (:refer-clojure :exclude [remove]) 3 | (:require [rewrite-clj.custom-zipper.core :as zraw] 4 | [rewrite-clj.custom-zipper.utils :as u] 5 | [rewrite-clj.zip.move :as m] 6 | [rewrite-clj.zip.whitespace :as ws])) 7 | 8 | #?(:clj (set! *warn-on-reflection* true)) 9 | 10 | (defn- node-depth 11 | "Return current node location depth in `zloc`, top is 0." 12 | [zloc] 13 | (->> (iterate zraw/up zloc) 14 | (take-while identity) 15 | count 16 | dec)) 17 | 18 | (defn- has-trailing-linebreak-at-eoi? 19 | "Returns true when current node is last node in zipper and trailing whitespace contains 20 | at least 1 newline." 21 | [zloc] 22 | (and (= 1 (node-depth zloc)) 23 | (not (m/right zloc)) 24 | (->> (iterate zraw/right zloc) 25 | (take-while identity) 26 | (some ws/linebreak?)))) 27 | 28 | (defn- left-ws-trim 29 | ([zloc] 30 | (left-ws-trim zloc ws/whitespace?)) 31 | ([zloc p?] 32 | (if (or (m/rightmost? zloc) 33 | (m/leftmost? zloc)) 34 | (u/remove-left-while zloc p?) 35 | zloc))) 36 | 37 | (defn- right-ws-trim 38 | ([zloc] 39 | (right-ws-trim zloc ws/whitespace?)) 40 | ([zloc p?] 41 | (u/remove-right-while zloc p?))) 42 | 43 | (defn- right-ws-trim-keep-trailing-linebreak [zloc] 44 | (let [right-trimmed (right-ws-trim zloc)] 45 | (if (has-trailing-linebreak-at-eoi? zloc) 46 | (ws/insert-newline-right right-trimmed) 47 | right-trimmed))) 48 | 49 | (defn- remove-with-trim 50 | [zloc left-ws-trim-fn right-ws-trim-fn] 51 | (->> zloc 52 | left-ws-trim-fn 53 | right-ws-trim-fn 54 | zraw/remove 55 | (ws/skip-whitespace zraw/prev))) 56 | 57 | (defn remove 58 | "Return `zloc` with current node removed. Returned zipper location 59 | is moved to the first non-whitespace node preceding removed node in a depth-first walk. 60 | Removes whitespace appropriately. 61 | 62 | - `[1 |2 3] => [|1 3]` 63 | - `[1 |2] => [|1]` 64 | - `[|1 2] => |[2]` 65 | - `[|1] => |[]` 66 | - `[ |1 ] => |[]` 67 | - `[1 [2 3] |4] => [1 [2 |3]]` 68 | - `[|1 [2 3] 4] => |[[2 3] 4]` 69 | 70 | If the removed node is a rightmost sibling, both leading and trailing whitespace 71 | is removed, otherwise only trailing whitespace is removed. 72 | 73 | The result is that a following element (no matter whether it is on the same line 74 | or not) will end up at same positon (line/column) as the removed one. 75 | If a comment lies betwen the original node and the neighbour this will not hold true. 76 | 77 | If the removed node is at end of input and is trailed by 1 or more newlines, 78 | a single trailing newline will be preserved. 79 | 80 | Use [[remove*]] to remove node without removing any surrounding whitespace." 81 | [zloc] 82 | {:pre [zloc] 83 | :post [%]} 84 | (remove-with-trim zloc 85 | left-ws-trim 86 | right-ws-trim-keep-trailing-linebreak)) 87 | 88 | (defn remove-and-move-left 89 | "Return `zloc` with current node removed, and located to node left of removed node. 90 | If no left node, returns `nil`. 91 | 92 | Currently internal, and likely not generic enough to expose, review update as necessary should we want to expose to public API." 93 | [zloc] 94 | (when (m/left zloc) 95 | (->> zloc 96 | left-ws-trim 97 | right-ws-trim 98 | u/remove-and-move-left 99 | ;; TODO: needed? 100 | (ws/skip-whitespace zraw/left)))) 101 | 102 | (defn remove-preserve-newline 103 | "Same as [[remove]] but preserves newlines. 104 | Specifically: will trim all whitespace - or whitespace up to first linebreak if present." 105 | [zloc] 106 | {:pre [zloc] 107 | :post [%]} 108 | (let [ws-pred-fn #(and (ws/whitespace? %) (not (ws/linebreak? %)))] 109 | (remove-with-trim zloc 110 | #(left-ws-trim % ws-pred-fn) 111 | #(right-ws-trim % ws-pred-fn)))) 112 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/seq.clj: -------------------------------------------------------------------------------- 1 | ;; DO NOT EDIT FILE, automatically generated from: ./template/rewrite_clj/zip/seq.clj 2 | (ns ^:no-doc rewrite-clj.zip.seq 3 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 4 | This ns does not work for cljs due to namespace collisions." 5 | (:refer-clojure :exclude [map get assoc seq? vector? list? map? set?]) 6 | (:require [rewrite-clj.zip.seqz] )) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | 11 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 12 | (defn seq? 13 | "Returns true if current node in `zloc` is a sequence." 14 | [zloc] (rewrite-clj.zip.seqz/seq? zloc)) 15 | 16 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 17 | (defn list? 18 | "Returns true if current node in `zloc` is a list." 19 | [zloc] (rewrite-clj.zip.seqz/list? zloc)) 20 | 21 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 22 | (defn vector? 23 | "Returns true if current node in `zloc` is a vector." 24 | [zloc] (rewrite-clj.zip.seqz/vector? zloc)) 25 | 26 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 27 | (defn set? 28 | "Returns true if current node in `zloc` is a set." 29 | [zloc] (rewrite-clj.zip.seqz/set? zloc)) 30 | 31 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 32 | (defn map? 33 | "Returns true if current node in `zloc` is a map." 34 | [zloc] (rewrite-clj.zip.seqz/map? zloc)) 35 | 36 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 37 | (defn map-vals 38 | "Returns `zloc` with function `f` applied to each value node of the current node. 39 | Current node must be map node. 40 | 41 | `zloc` location is unchanged. 42 | 43 | `f` arg is zloc positioned at value node and should return: 44 | - an updated zloc with zloc positioned at value node 45 | - a falsey value to leave value node unchanged 46 | 47 | Folks typically use [[edit]] for `f`." 48 | [f zloc] (rewrite-clj.zip.seqz/map-vals f zloc)) 49 | 50 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 51 | (defn map-keys 52 | "Returns `zloc` with function `f` applied to all key nodes of the current node. 53 | Current node must be map node. 54 | 55 | `zloc` location is unchanged. 56 | 57 | `f` arg is zloc positioned at key node and should return: 58 | - an updated zloc with zloc positioned at key node 59 | - a falsey value to leave value node unchanged 60 | 61 | Folks typically use [[rewrite-clj.zip/edit]] for `f`." 62 | [f zloc] (rewrite-clj.zip.seqz/map-keys f zloc)) 63 | 64 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 65 | (defn map 66 | "Returns `zloc` with function `f` applied to all nodes of the current node. 67 | Current node must be a sequence node. Equivalent to [[rewrite-clj.zip/map-vals]] for maps. 68 | 69 | `zloc` location is unchanged. 70 | 71 | `f` arg is zloc positioned at 72 | - value nodes for maps 73 | - each element of a seq 74 | and is should return: 75 | - an updated zloc with zloc positioned at edited node 76 | - a falsey value to leave value node unchanged 77 | 78 | Folks typically use [[edit]] for `f`." 79 | [f zloc] (rewrite-clj.zip.seqz/map f zloc)) 80 | 81 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 82 | (defn get 83 | "Returns `zloc` located to map key node's sexpr value matching `k` else `nil`. 84 | 85 | `k` should be: 86 | - a key for maps 87 | - a zero-based index for sequences 88 | 89 | NOTE: `k` will be compared against resolved keywords in maps. 90 | See docs for sexpr behavior on [namespaced elements](/doc/01-user-guide.adoc#namespaced-elements)." 91 | [zloc k] (rewrite-clj.zip.seqz/get zloc k)) 92 | 93 | ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.seqz 94 | (defn assoc 95 | "Returns `zloc` with current node's `k` set to value `v`. 96 | 97 | `zloc` location is unchanged. 98 | 99 | `k` should be: 100 | - a key for maps 101 | - a zero-based index for sequences, an exception is thrown if index is out of bounds 102 | 103 | NOTE: `k` will be compared against resolved keywords in maps. 104 | See docs for sexpr behavior on [namespaced elements](/doc/01-user-guide.adoc#namespaced-elements)." 105 | [zloc k v] (rewrite-clj.zip.seqz/assoc zloc k v)) 106 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/subedit.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.subedit 2 | (:require [rewrite-clj.custom-zipper.core :as zraw] 3 | [rewrite-clj.zip.base :as base] 4 | [rewrite-clj.zip.options :as options]) 5 | #?(:cljs (:require-macros [rewrite-clj.zip.subedit])) ) 6 | 7 | #?(:clj (set! *warn-on-reflection* true)) 8 | 9 | ;; ## Edit Scope 10 | 11 | (defn- path 12 | "Generate a seq representing a path to the current node 13 | starting at the root. Each element represents one `z/down` 14 | and the value of each element will be the number of `z/right`s 15 | to run." 16 | [zloc] 17 | (->> (iterate zraw/up zloc) 18 | (take-while zraw/up) 19 | (map (comp count zraw/lefts)) 20 | (reverse))) 21 | 22 | (defn- move-step 23 | "Move one down and `n` steps to the right." 24 | [loc n] 25 | (nth 26 | (iterate zraw/right (zraw/down loc)) 27 | n)) 28 | 29 | (defn- move-to 30 | "Move to the node represented by the given path." 31 | [zloc path] 32 | (let [root (-> zloc 33 | zraw/root 34 | (base/of-node* (options/get-opts zloc)))] 35 | (reduce move-step root path))) 36 | 37 | (defn edit-node 38 | "Return zipper applying function `f` to `zloc`. The resulting 39 | zipper will be located at the same path (i.e. the same number of 40 | downwards and right movements from the root) incoming `zloc`. 41 | 42 | See also [[subedit-node]] for an isolated edit." 43 | [zloc f] 44 | (let [zloc' (f zloc)] 45 | (assert (not (nil? zloc')) "function applied in 'edit-node' returned nil.") 46 | (move-to zloc' (path zloc)))) 47 | 48 | (defmacro edit-> 49 | "Like `->`, threads `zloc` through forms. 50 | The resulting zipper will be located at the same path (i.e. the same 51 | number of downwards and right movements from the root) as incoming `zloc`. 52 | 53 | See also [[subedit->]] for an isolated edit." 54 | [zloc & body] 55 | `(edit-node ~zloc #(-> % ~@body))) 56 | 57 | (defmacro edit->> 58 | "Like `->>`, threads `zloc` through forms. 59 | The resulting zipper will be located at the same path (i.e. the same 60 | number of downwards and right movements from the root) as incoming `zloc`. 61 | 62 | See also [[subedit->>]] for an isolated edit." 63 | [zloc & body] 64 | `(edit-node ~zloc #(->> % ~@body))) 65 | 66 | ;; ## Sub-Zipper 67 | 68 | (defn subzip 69 | "Create and return a zipper whose root is the current node in `zloc`. 70 | 71 | See [docs on sub editing](/doc/01-user-guide.adoc#sub-editing)." 72 | [zloc] 73 | (let [zloc' (some-> zloc 74 | zraw/node 75 | (base/of-node* (options/get-opts zloc)))] 76 | (assert zloc' "could not create subzipper.") 77 | zloc')) 78 | 79 | (defn subedit-node 80 | "Return zipper replacing current node in `zloc` with result of `f` applied to said node as an isolated sub-tree. 81 | The resulting zipper will be located on the root of the modified sub-tree. 82 | 83 | See [docs on sub editing](/doc/01-user-guide.adoc#sub-editing)." 84 | [zloc f] 85 | (let [zloc' (f (subzip zloc))] 86 | (assert (not (nil? zloc')) "function applied in 'subedit-node' returned nil.") 87 | (zraw/replace zloc (zraw/root zloc')))) 88 | 89 | (defmacro subedit-> 90 | "Like `->`, threads `zloc`, as an isolated sub-tree through forms, then zips 91 | up to, and locates at, the root of the modified sub-tree. 92 | 93 | See [docs on sub editing](/doc/01-user-guide.adoc#sub-editing)." 94 | [zloc & body] 95 | `(subedit-node ~zloc #(-> % ~@body))) 96 | 97 | (defmacro subedit->> 98 | "Like `->>`. Threads `zloc`, as an isolated sub-tree through forms, then zips 99 | up to, and locates at, the root of the modified sub-tree. 100 | 101 | See [docs on sub editing](/doc/01-user-guide.adoc#sub-editing)." 102 | [zloc & body] 103 | `(subedit-node ~zloc #(->> % ~@body))) 104 | -------------------------------------------------------------------------------- /src/rewrite_clj/zip/walk.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.walk 2 | (:require [rewrite-clj.zip.move :as m] 3 | [rewrite-clj.zip.subedit :as subedit])) 4 | 5 | #?(:clj (set! *warn-on-reflection* true)) 6 | 7 | (defn- downmost [zloc] 8 | (->> (iterate m/down zloc) 9 | (take-while identity) 10 | last)) 11 | 12 | (defn- process-loc [zloc p? f] 13 | (if (p? zloc) 14 | (or (f zloc) zloc) 15 | zloc)) 16 | 17 | (defn- prewalk-subtree 18 | [p? f zloc] 19 | (loop [loc zloc] 20 | (if (m/end? loc) 21 | loc 22 | (recur (-> loc 23 | (process-loc p? f) 24 | m/next))))) 25 | 26 | (defn prewalk 27 | "Return zipper modified by an isolated depth-first pre-order traversal. 28 | 29 | Pre-order traversal visits root before children. 30 | For example, traversal order of `(1 (2 3 (4 5) 6 (7 8)) 9)` is: 31 | 32 | 1. `(1 (2 3 (4 5) 6 (7 8)) 9)` 33 | 2. `1` 34 | 3. `(2 3 (4 5) 6 (7 8))` 35 | 4. `2` 36 | 5. `3` 37 | 6. `(4 5)` 38 | 7. `4` 39 | 8. `5` 40 | 9. `6` 41 | 10. `(7 8)` 42 | 11. `7` 43 | 12. `8` 44 | 13. `9` 45 | 46 | Traversal starts at the current node in `zloc` and continues to the end of the isolated sub-tree. 47 | 48 | Function `f` is called on the zipper locations satisfying predicate `p?` and must return either 49 | - nil to indicate no changes 50 | - or a valid zipper 51 | WARNING: when function `f` changes the location in the zipper, normal traversal will be affected. 52 | 53 | When `p?` is not specified `f` is called on all locations. 54 | 55 | To walk all nodes, you'll want to walk from the root node. 56 | You can do this by, for example, using [[of-string*]] instead of [[of-string]]. 57 | 58 | ```Clojure 59 | (-> (zip/of-string* \"my clojure forms\") 60 | (zip/prewalk ...)) 61 | ``` 62 | 63 | See [docs on sub editing](/doc/01-user-guide.adoc#sub-editing)." 64 | ([zloc f] (prewalk zloc (constantly true) f)) 65 | ([zloc p? f] 66 | (->> (partial prewalk-subtree p? f) 67 | (subedit/subedit-node zloc)))) 68 | 69 | (defn postwalk-subtree 70 | [p? f zloc] 71 | (loop [loc (downmost zloc)] 72 | (let [loc (process-loc loc p? f)] 73 | (cond (m/right loc) 74 | (recur (-> loc m/right downmost)) 75 | 76 | (m/up loc) 77 | (recur (m/up loc)) 78 | 79 | :else 80 | loc)))) 81 | 82 | (defn postwalk 83 | "Return zipper modified by an isolated depth-first post-order traversal. 84 | 85 | Post-order traversal visits children before root. 86 | For example, traversal order of `(1 (2 3 (4 5) 6 (7 8)) 9)` is: 87 | 88 | 1. `1` 89 | 2. `2` 90 | 3. `3` 91 | 4. `4` 92 | 5. `5` 93 | 6. `(4 5)` 94 | 7. `6` 95 | 8. `7` 96 | 9. `8` 97 | 10. `(7 8)` 98 | 11. `(2 3 (4 5) 6 (7 8))` 99 | 12. `9` 100 | 13. `(1 (2 3 (4 5) 6 (7 8)) 9)` 101 | 102 | Traversal starts at the current node in `zloc` and continues to the end of the isolated sub-tree. 103 | 104 | Function `f` is called on the zipper locations satisfying predicate `p?` and must return either 105 | - nil to indicate no changes 106 | - or a valid zipper 107 | WARNING: when function `f` changes the location in the zipper, normal traversal will be affected. 108 | 109 | When `p?` is not specified `f` is called on all locations. 110 | 111 | To walk all nodes, you'll want to walk from the root node. 112 | You can do this by, for example, using [[of-string*]] instead of [[of-string]]. 113 | 114 | ```Clojure 115 | (-> (zip/of-string* \"my clojure forms\") 116 | (zip/postwalk ...)) 117 | ``` 118 | 119 | See [docs on sub editing](/doc/01-user-guide.adoc#sub-editing)." 120 | ([zloc f] (postwalk zloc (constantly true) f)) 121 | ([zloc p? f] 122 | (subedit/subedit-node zloc #(postwalk-subtree p? f %)))) 123 | -------------------------------------------------------------------------------- /template/rewrite_clj/node/string.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.node.string 2 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 3 | This ns does not work for cljs due to namespace collisions." 4 | (:require [rewrite-clj.node.stringz])) 5 | 6 | (set! *warn-on-reflection* true) 7 | 8 | #_{:import-vars/import 9 | {:from [[rewrite-clj.node.stringz 10 | string-node]]}} 11 | -------------------------------------------------------------------------------- /template/rewrite_clj/zip/edit.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.edit 2 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 3 | This ns does not work for cljs due to namespace collisions." 4 | (:refer-clojure :exclude [replace]) 5 | (:require [rewrite-clj.zip.editz])) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | #_{:import-vars/import 10 | {:from [[rewrite-clj.zip.editz 11 | replace 12 | edit 13 | splice 14 | prefix 15 | suffix]]}} 16 | -------------------------------------------------------------------------------- /template/rewrite_clj/zip/find.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.find 2 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 3 | This ns does not work for cljs due to namespace collisions." 4 | (:refer-clojure :exclude [find]) 5 | (:require [rewrite-clj.zip.findz])) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | #_{:import-vars/import 10 | {:from [[rewrite-clj.zip.findz 11 | find 12 | find-last-by-pos 13 | find-depth-first 14 | find-next 15 | find-next-depth-first 16 | find-tag 17 | find-next-tag 18 | find-tag-by-pos 19 | find-token 20 | find-next-token 21 | find-value 22 | find-next-value]]}} 23 | -------------------------------------------------------------------------------- /template/rewrite_clj/zip/remove.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.remove 2 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 3 | This ns does not work for cljs due to namespace collisions." 4 | (:refer-clojure :exclude [remove]) 5 | (:require [rewrite-clj.zip.removez])) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | #_{:import-vars/import 10 | {:from [[rewrite-clj.zip.removez 11 | remove 12 | remove-preserve-newline]]}} 13 | -------------------------------------------------------------------------------- /template/rewrite_clj/zip/seq.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc rewrite-clj.zip.seq 2 | "This ns exists to preserve compatability for rewrite-clj v0 clj users who were using an internal API. 3 | This ns does not work for cljs due to namespace collisions." 4 | (:refer-clojure :exclude [map get assoc seq? vector? list? map? set?]) 5 | (:require [rewrite-clj.zip.seqz] )) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | #_{:import-vars/import 10 | {:from [[rewrite-clj.zip.seqz 11 | seq? 12 | list? 13 | vector? 14 | set? 15 | map? 16 | map-vals 17 | map-keys 18 | map 19 | get 20 | assoc]]}} 21 | -------------------------------------------------------------------------------- /test-isolated/rewrite_clj/zip_solo_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.zip-solo-test 2 | (:require [clojure.test :refer [deftest is]] 3 | ;; leave only rewrite-clj.zip required for these tests 4 | [rewrite-clj.zip :as z])) 5 | 6 | (deftest t-can-use-coercions-without-node-api-explicitly-required 7 | (is (= "[1 2 [3 4]]" (-> "[1 2]" 8 | z/of-string 9 | (z/append-child [3 4]) 10 | z/root-string)))) 11 | -------------------------------------------------------------------------------- /test/rewrite_clj/hello_figwheel.cljs: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; I find figwheel main's test support quite nice while developing. 3 | ;; We have no app here though, just a library and fighweel main needs a main to work. 4 | ;; Hence this file. 5 | ;; 6 | (ns rewrite-clj.hello-figwheel) 7 | 8 | (js/console.log "hello figwheel") 9 | -------------------------------------------------------------------------------- /test/rewrite_clj/node/equals_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.node.equals-test 2 | (:require 3 | [clojure.test :refer [deftest is]] 4 | [rewrite-clj.node :as node])) 5 | 6 | (deftest equals-test 7 | (is (= (node/list-node []) (node/list-node []))) 8 | (is (= (node/vector-node []) (node/vector-node []))) 9 | (is (= (node/set-node []) (node/set-node []))) 10 | (is (= (node/map-node []) (node/map-node [])))) 11 | -------------------------------------------------------------------------------- /test/rewrite_clj/node/integer_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.node.integer-test 2 | (:require [clojure.test.check.clojure-test :refer [defspec]] 3 | [clojure.test.check.properties :as prop #?@(:cljs [:include-macros true])] 4 | [clojure.tools.reader.edn :as edn] 5 | [rewrite-clj.node :as node] 6 | [rewrite-clj.node.generators :as g])) 7 | 8 | (defspec t-all-integer-nodes-produce-readable-strings 100 9 | (prop/for-all [node g/integer-node] 10 | (edn/read-string (node/string node)))) 11 | -------------------------------------------------------------------------------- /test/rewrite_clj/node/node_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.node.node-test 2 | (:require [clojure.test.check.clojure-test #?(:clj :refer :cljs :refer-macros) [defspec]] 3 | #?(:cljs [clojure.test.check :refer-macros [quick-check]]) 4 | [clojure.test.check.properties :as prop #?@(:cljs [:include-macros true])] 5 | [rewrite-clj.node :as node] 6 | [rewrite-clj.node.generators :as g])) 7 | 8 | (defspec t-nodes-with-children-report-accurate-leader-lengths 9 | (prop/for-all [node (g/node g/container-node-types)] 10 | (let [node-str (node/string node) 11 | children-str (apply str (map node/string (node/children node))) 12 | leader (node/leader-length node)] 13 | (= (subs node-str leader (+ leader (count children-str))) 14 | children-str)))) 15 | -------------------------------------------------------------------------------- /test/rewrite_clj/transform_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.transform-test 2 | (:require [clojure.string :as string] 3 | [clojure.test :refer [deftest testing is]] 4 | [rewrite-clj.zip :as z])) 5 | 6 | (deftest t-transform 7 | (let [data-string (str ";; This is a Project File.\n" 8 | "(defproject my-project \"0.1.0-SNAPSHOT\"\n" 9 | " :description \"A project.\n" 10 | " Multiline!\"\n" 11 | " :dependencies [[a \"0.1.0\"]\n" 12 | " [b \"1.2.3\"]]\n" 13 | " :repositories { \"private\" \"http://private.com/repo\" })") 14 | data (z/of-string data-string)] 15 | 16 | (testing "simple transformation" 17 | (is (= (string/replace data-string "SNAPSHOT" "SNAPSHOT-1") 18 | (-> data 19 | (z/find-value z/next 'defproject) 20 | z/right z/right 21 | (z/edit #(str % "-1")) 22 | z/root-string)))) 23 | 24 | (testing "seq transformation prefix" 25 | (is (= (-> data-string 26 | (string/replace "[a " "[prefix-a ") 27 | (string/replace "[b " "[prefix-b ")) 28 | (-> data 29 | (z/find-value z/next 'defproject) 30 | (z/find-value :dependencies) z/right 31 | (->> 32 | (z/map 33 | (fn [loc] 34 | (-> loc z/down 35 | (z/prefix "prefix-") 36 | z/up)))) 37 | z/root-string)))) 38 | 39 | (testing "seq transformation suffix" 40 | (is (= (-> data-string 41 | (string/replace "[a " "[a-suffix ") 42 | (string/replace "[b " "[b-suffix ")) 43 | (-> data 44 | (z/find-value z/next 'defproject) 45 | (z/find-value :dependencies) z/right 46 | (->> 47 | (z/map 48 | (fn [loc] 49 | (-> loc z/down 50 | (z/suffix "-suffix") 51 | z/up)))) 52 | z/root-string)))) 53 | 54 | (testing "whitespace handling in removal" 55 | (is (= (string/replace data-string #"\[a [^\s]+\]\s+" "") 56 | (-> data 57 | (z/find-value z/next 'defproject) 58 | (z/find-value :dependencies) 59 | z/right z/down z/remove 60 | z/root-string)))))) 61 | -------------------------------------------------------------------------------- /test/rewrite_clj/zip/move_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.zip.move-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [rewrite-clj.zip :as z])) 4 | 5 | (deftest t-whitespace-aware-movement 6 | (let [root (z/of-string "[ 1 [2 3] 4]")] 7 | (doseq [[ops sexpr pred] 8 | [[[z/down] 1 identity] 9 | [[z/next] 1 (complement z/end?)] 10 | [[z/next z/next] [2 3] (complement z/end?)] 11 | [[z/down z/right z/right] 4 (complement z/end?)] 12 | [[z/down z/rightmost] 4 z/rightmost?] 13 | [[z/down z/rightmost z/rightmost] 4 z/rightmost] 14 | [[z/down z/leftmost] 1 z/leftmost] 15 | [[z/down z/right z/leftmost] 1 z/leftmost?] 16 | [[z/down z/next z/next z/up] [2 3] identity] 17 | [[z/down z/next z/next z/up z/up] [1 [2 3] 4] #(and (z/leftmost? %) (z/rightmost? %))] 18 | [[z/down z/rightmost z/prev] 3 identity] 19 | [[z/down z/rightmost z/next] 4 z/end?]]] 20 | (let [loc ((apply comp (reverse ops)) root)] 21 | (is (= sexpr (z/sexpr loc))) 22 | (is (pred loc)))))) 23 | 24 | (deftest t-moving-into-an-empty-inner-node 25 | (let [zloc (z/of-string "[]")] 26 | (is (nil? (z/down zloc))))) 27 | -------------------------------------------------------------------------------- /test/rewrite_clj/zip/removez_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.zip.removez-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [rewrite-clj.zip :as z])) 4 | 5 | (deftest t-whitespace-aware-removal 6 | (doseq [[in n expected-out expected-out-preserve-newline] 7 | [["[1 2 3 4]" 0 "" ""] 8 | ["[1 2 3 4]" 1 "[2 3 4]" "[2 3 4]"] 9 | ["[1 2 3 4]" 2 "[1 3 4]" "[1 3 4]"] 10 | ["[1 2 3 4]" 3 "[1 2 4]" "[1 2 4]"] 11 | ["[1 2 3 4]" 4 "[1 2 3]" "[1 2 3]"] 12 | ["[ 1 2 3 4]" 1 "[2 3 4]" "[2 3 4]"] 13 | ["[1 2 3 4 ]" 4 "[1 2 3]" "[1 2 3]"] 14 | ["[1]" 1 "[]" "[]"] 15 | ["[ 1 ]" 1 "[]" "[]"] 16 | ["[\n \n 1 \n \n]" 1 "[]" "[\n \n\n \n]"] 17 | ["[\n \n 1 \n \n 2 \n\n]" 2 "[\n \n 1]" "[\n \n 1 \n \n\n\n]"] 18 | ["[;; c\n1]" 1 "[;; c\n]" "[;; c\n]"] 19 | ["[1\n;; c\n2]" 1 "[;; c\n2]" "[\n;; c\n2]"] 20 | ["[1\n;; c\n2]" 2 "[1\n;; c\n]" "[1\n;; c\n]"] 21 | ["[1\n;; c\n2]" 1 "[;; c\n2]" "[\n;; c\n2]"]]] 22 | (let [elements (->> (z/of-string in) 23 | (iterate z/next)) 24 | loc (nth elements n)] 25 | (is (= expected-out 26 | (-> loc z/remove z/root-string)) 27 | "remove") 28 | (is (= expected-out-preserve-newline 29 | (-> loc z/remove-preserve-newline z/root-string)) 30 | "remove-preserve-newline")))) 31 | 32 | (deftest t-more-whitespace 33 | (let [root (z/of-string 34 | (str " :k [[a b c]\n" 35 | " [d e f]]\n" 36 | " :keyword 0"))] 37 | (is (= (str " :k [[d e f]]\n" 38 | " :keyword 0") 39 | (-> root z/next z/down z/remove z/root-string))))) 40 | 41 | (deftest t-removing-after-comment 42 | (let [loc (-> (z/of-string "; comment\nx") 43 | (z/rightmost) 44 | (z/remove))] 45 | (is (= "; comment\n" (z/root-string loc))))) 46 | 47 | (deftest t-removing-at-end-of-input-preserves-an-existing-newline-at-end-of-input 48 | (doseq [[in expected-out expected-out-preserve-newline] 49 | [["(def a 1) (del-me b 2)" 50 | "(def a 1)" 51 | "(def a 1)"] 52 | ["(def a 1) (del-me b 2)\n" 53 | "(def a 1)\n" 54 | "(def a 1)\n"] 55 | ["(def a 1)\n(del-me b 2)" 56 | "(def a 1)" 57 | "(def a 1)\n"] 58 | ["(def a 1)\n\n\n(del-me b 2)" 59 | "(def a 1)" 60 | "(def a 1)\n\n\n"] 61 | ["(def a 1) (del-me b 2)\n\n\n" 62 | "(def a 1)\n" 63 | "(def a 1)\n\n\n"] 64 | ["(def a 1)\n\n\n(del-me b 2) \n\n\n" 65 | "(def a 1)\n" 66 | "(def a 1)\n\n\n\n\n\n"] 67 | ["(def a 1)\n\n\n(def b 2)\n\n\n(del-me c 3)" 68 | "(def a 1)\n\n\n(def b 2)" 69 | "(def a 1)\n\n\n(def b 2)\n\n\n"] 70 | ["(def a 1)\n\n\n(def b 2)\n\n\n(del-me c 3) \n \n" 71 | "(def a 1)\n\n\n(def b 2)\n" 72 | "(def a 1)\n\n\n(def b 2)\n\n\n\n \n"] 73 | ["(def a 1)\n\n(del-me b 2)\n;; a comment\n" 74 | "(def a 1)\n;; a comment\n" 75 | "(def a 1)\n\n\n;; a comment\n"]]] 76 | (let [zloc (->> in 77 | z/of-string 78 | z/rightmost)] 79 | (is (= expected-out (->> zloc z/remove z/root-string)) "remove") 80 | (is (= expected-out-preserve-newline (->> zloc z/remove-preserve-newline z/root-string)) "remove-preserve-newline")))) 81 | -------------------------------------------------------------------------------- /test/rewrite_clj/zip/subedit_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.zip.subedit-test 2 | (:require [clojure.test :refer [deftest testing is]] 3 | [rewrite-clj.zip :as z] 4 | [rewrite-clj.zip.options :as options])) 5 | 6 | (deftest t-trees 7 | (let [root (z/of-string "[1 #{2 [3 4] 5} 6]")] 8 | (testing "modifying subtrees" 9 | (let [loc (z/subedit-> root 10 | z/next 11 | z/next 12 | z/next 13 | (z/replace* 'x))] 14 | (is (= :vector (z/tag loc))) 15 | (is (= "[1 #{x [3 4] 5} 6]" (z/string loc))))) 16 | (testing "modifying the whole tree" 17 | (let [loc (z/edit-> (-> root z/next z/next z/next) 18 | z/prev z/prev 19 | (z/replace* 'x))] 20 | (is (= :token (z/tag loc))) 21 | (is (= "2" (z/string loc))) 22 | (is (= "[x #{2 [3 4] 5} 6]" (z/root-string loc))))))) 23 | 24 | (deftest zipper-retains-options 25 | (let [zloc (z/of-string "(1 (2 (3 4 ::my-kw)))" {:auto-resolve (fn [_x] 'custom-resolved)}) 26 | orig-opts (options/get-opts zloc)] 27 | (testing "sanity - without subzip" 28 | (is (= :custom-resolved/my-kw (-> zloc 29 | z/down z/right 30 | z/down z/right 31 | z/down z/rightmost z/sexpr)))) 32 | (testing "subzip" 33 | (let [sub-zloc (-> zloc z/up* z/subzip z/down*)] 34 | (is (= orig-opts (options/get-opts sub-zloc))) 35 | (is (= :custom-resolved/my-kw (-> sub-zloc 36 | z/down z/right 37 | z/down z/right 38 | z/down z/rightmost z/sexpr))))) 39 | (testing "edit-node" 40 | (let [edited-zloc (-> zloc (z/edit-node 41 | (fn [zloc-edit] 42 | (-> zloc-edit 43 | z/down z/right 44 | z/down (z/replace* 'x)))))] 45 | (is (= 'x (-> edited-zloc z/down z/right z/down z/sexpr))) 46 | (is (= orig-opts (options/get-opts edited-zloc))) 47 | (is (= :custom-resolved/my-kw (-> edited-zloc 48 | z/down z/right 49 | z/down z/right 50 | z/down z/rightmost z/sexpr))))))) 51 | -------------------------------------------------------------------------------- /test/rewrite_clj/zip/test_helper.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.zip.test-helper 2 | "It can be tricky and error prone to navigate to a desired location in a zipper. 3 | These helpers allow us to mark our desired location with a special ⊚ character 4 | and spit out that same character to reflect the location. 5 | The ◬ character at start of string to indicates root :forms node location." 6 | (:require [clojure.string :as str] 7 | [rewrite-clj.node :as n] 8 | [rewrite-clj.zip :as z])) 9 | 10 | (defn pos-and-s 11 | "Given `s` that includes a single loc prefix ⊚, return map with 12 | - `:s` s without loc prefix char 13 | - `:pos` 14 | - `:row` row of prefix char 15 | - `:col` col or prefix char" 16 | [s] 17 | (when-let [marker-ndx (str/index-of s "⊚")] 18 | (let [[row col] (-> (subs s 0 marker-ndx) 19 | (str/split #"\n" -1) 20 | ((juxt count #(-> % last count inc) )))] 21 | {:s (str/replace s "⊚" "") :pos {:row row :col col}}))) 22 | 23 | (defn of-locmarked-string 24 | "Return zloc for string `s` located at node prefixed with ⊚ marker. 25 | Use ◬ as first char to locate to root forms node." 26 | [s opts] 27 | 28 | (cond 29 | (str/starts-with? s "◬") 30 | (z/of-string* (subs s 1) opts) 31 | 32 | (str/includes? s "⊚") 33 | (let [{:keys [pos s]} (pos-and-s s) 34 | {target-row :row target-col :col} pos 35 | zloc (z/of-string* s opts)] 36 | (loop [zloc (z/down* zloc)] 37 | (let [{:keys [row col]} (meta (z/node zloc))] 38 | (cond 39 | (and (= target-row row) (= target-col col)) 40 | zloc 41 | 42 | (z/end? zloc) 43 | (throw (ex-info (str "Oops, of-locmarked-string failed to locate to node at ⊚ mark found at [row col]:" [target-row target-col]) {})) 44 | 45 | :else 46 | (recur (z/next* zloc)))))) 47 | :else 48 | (throw (ex-info "s needs to start with ◬, or include a single ⊚" {})))) 49 | 50 | (defn- row-num [zloc] 51 | (loop [zloc (z/prev* zloc) 52 | rows 0] 53 | (if (not zloc) 54 | (inc rows) 55 | (cond 56 | (z/linebreak? zloc) 57 | (recur (z/prev* zloc) (long (+ rows (z/length zloc)))) 58 | 59 | (n/comment? (z/node zloc)) 60 | (recur (z/prev* zloc) (inc rows)) 61 | 62 | :else 63 | (recur (z/prev* zloc) rows))))) 64 | 65 | (defn- col-num [zloc] 66 | (loop [zloc zloc 67 | cols 0] 68 | (let [up (z/up* zloc) 69 | left (z/left* zloc)] 70 | (cond 71 | (and left (or (z/linebreak? left) (-> left z/node n/comment?))) 72 | (inc cols) 73 | 74 | left 75 | (recur left (long (+ cols (z/length left)))) 76 | 77 | up 78 | (recur up (long (+ cols (-> up z/node n/leader-length)))) 79 | 80 | :else 81 | (inc cols))))) 82 | 83 | (defn root-locmarked-string 84 | "Return root string for `zloc` with current node prefixed with ⊚ marker, 85 | if located at root forms node string will start with ◬" 86 | [zloc] 87 | (if (= :forms (z/tag zloc)) 88 | (str "◬" (z/root-string zloc) ) 89 | (let [row (row-num zloc) 90 | col (col-num zloc) 91 | s (z/root-string zloc) 92 | lines (str/split s #"\n" -1) 93 | line (nth lines (dec row))] 94 | (str/join "\n" 95 | (concat 96 | (subvec lines 0 (dec row)) 97 | [(str (subs line 0 (dec col)) 98 | "⊚" 99 | (subs line (dec col)))] 100 | (subvec lines row)))))) 101 | -------------------------------------------------------------------------------- /test/rewrite_clj/zip/whitespace_test.cljc: -------------------------------------------------------------------------------- 1 | (ns rewrite-clj.zip.whitespace-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [rewrite-clj.node :as node] 4 | [rewrite-clj.zip :as z])) 5 | 6 | (deftest whitespace 7 | (let [space (node/spaces 1) 8 | linebreak (node/newlines 1) 9 | c0 (node/comment-node "comment") 10 | t0 (node/token-node 0) 11 | t1 (node/token-node 1) 12 | loc (z/of-node* (node/forms-node [space linebreak t0 space t1 c0]))] 13 | (testing "predicates" 14 | (is (z/whitespace? (-> loc z/down*))) 15 | (is (z/linebreak? (-> loc z/down* z/right*))) 16 | (is (z/whitespace-or-comment? (-> loc z/down* z/rightmost*)))) 17 | (testing "skipping whitespace to the right" 18 | (let [n (-> loc z/down* z/skip-whitespace z/node)] 19 | (is (= :token (node/tag n))) 20 | (is (= 0 (node/sexpr n))))) 21 | (testing "skipping whitespace to the left" 22 | (let [n (-> loc z/down* z/rightmost* z/skip-whitespace-left z/node)] 23 | (is (= :token (node/tag n))) 24 | (is (= 1 (node/sexpr n))))) 25 | (testing "skipping whitespace with a movement function" 26 | (let [n (->> loc z/down* (z/skip-whitespace z/next*) z/node)] 27 | (is (= :token (node/tag n))) 28 | (is (= 0 (node/sexpr n))))) 29 | (testing "prepending appending spaces" 30 | (doseq [[left-fn right-fn] 31 | [[z/prepend-space z/append-space] 32 | [z/insert-space-left z/insert-space-right]]] 33 | (let [n (-> loc z/down* z/rightmost* (left-fn 3))] 34 | (is (= " \n0 1 ;comment" (z/root-string n)))) 35 | (let [n (-> loc z/down* z/rightmost* (right-fn 3))] 36 | (is (= " \n0 1;comment " (z/root-string n)))))) 37 | (testing "prepending appending linebreaks" 38 | (doseq [[left-fn right-fn] 39 | [[z/prepend-newline z/append-newline] 40 | [z/insert-newline-left z/insert-newline-right]]] 41 | (let [n (-> loc z/down* z/rightmost* (left-fn 3))] 42 | (is (= " \n0 1\n\n\n;comment" (z/root-string n)))) 43 | (let [n (-> loc z/down* z/rightmost* (right-fn 3))] 44 | (is (= " \n0 1;comment\n\n\n" (z/root-string n)))))))) 45 | -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | ;; kaocha config for clj testing 2 | #kaocha/v1 3 | {:tests [#profile {:default {:id :unit 4 | :source-paths ["src"] 5 | :test-paths ["test"]} 6 | :test-isolated {:id :test-isolated 7 | :source-paths ["src"] 8 | :test-paths ["test-isolated"]} 9 | :test-docs {:id :docs 10 | :test-paths ["target/test-doc-blocks/test"]} 11 | :coverage {:id :coverage 12 | :test-paths ["test" "target/test-doc-blocks/test"]}}] 13 | :reporter kaocha.report.progress/report} 14 | --------------------------------------------------------------------------------