Skip to content

Commit

Permalink
cider and AST for smarter refactorings
Browse files Browse the repository at this point in the history
- very very first draft (more rough edges than achievements)
- starts up a clj-refactor cider repl session when clj-refactor minor
  mode is switched on -- presumably only once
- provides a minimal clj project and user namespace with helper
  functions
- demonstrates the usage of the cider session when searching for
  referred functions when removing not used requires
  • Loading branch information
benedekfazekas committed Apr 21, 2014
1 parent 01a338a commit cdf1c16
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 5 deletions.
27 changes: 22 additions & 5 deletions clj-refactor.el
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@
(require 'paredit)
(require 'multiple-cursors)
(require 'clojure-mode)
;; uncomment this if you want to try clj-refactor with cider session
;; do not run the tests tho with this uncommented
;;(require 'cider)

(defcustom cljr-add-ns-to-blank-clj-files t
"When true, automatically adds a ns form to new clj files."
Expand Down Expand Up @@ -470,9 +473,14 @@ errors."
(replace-regexp-in-string "\\[?]?" "" sexp))

(defun cljr--is-name-in-use-p (name)
(goto-char (point-min))
(let ((e (cljr--extract-sexp-content name)))
(when (re-search-forward (cljr--req-element-regexp e "[^[:word:]^-]") nil t) e)))
(cljr--goto-ns)
(paredit-forward)
(let ((body (replace-regexp-in-string "\".*\"" "" (buffer-substring-no-properties (point) (point-max))))
(e (cljr--extract-sexp-content name)))
(when (cider-eval-and-get-value (format "(find-referred \"%s\" \"%s\")"
body
e))
e)))

(defun cljr--rectify-refer-type-require (sexp-as-list refer-index as-used as-index)
(let* ((as-after-refer (and as-used (> as-index refer-index)))
Expand Down Expand Up @@ -1413,12 +1421,21 @@ front of function literals and sets."
(when find-file-p
(kill-buffer)))))))

;; ------ minor mode -----------
;;;###autoload
(defun cljr-init-clj-refactor-cider-repl ()
(let* ((clj-refactor-full-path (locate-library "clj-refactor"))
(clj-refactor-dir (substring clj-refactor-full-path 0 (s-index-of "clj-refactor.el" clj-refactor-full-path))))
(when (and (fboundp 'cider-jack-in)
(not (cider-find-connection-buffer-for-project-directory clj-refactor-dir)))
(let ((proj-file-buff (find-file-noselect (concat clj-refactor-dir "project.clj"))))
(with-current-buffer proj-file-buff
(cider-jack-in))))))

;; ------ minor mode -----------
;;;###autoload
(define-minor-mode clj-refactor-mode
"A mode to keep the clj-refactor keybindings."
nil " cljr" clj-refactor-map)
nil " cljr" clj-refactor-map :after-hook (cljr-init-clj-refactor-cider-repl))

(provide 'clj-refactor)
;;; clj-refactor.el ends here
77 changes: 77 additions & 0 deletions dev/user.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
(ns user
(:refer-clojure :exclude [macroexpand-1 read read-string])
(:require [clojure.java.io :as io]
[clojure.tools.analyzer :as ana]
[clojure.tools.analyzer.utils :refer [resolve-var]]
[clojure.tools.analyzer.ast :refer :all]
[clojure.tools.reader :as r])
(:import java.io.PushbackReader))

;; will I need this?
;; perhaps just get the ns as string from the emacs buffer
(defn read-all
[input]
(let [eof (Object.)]
(take-while #(not= % eof) (repeatedly #(read input false eof)))))

(defn read-all-file [file] (-> file io/reader (PushbackReader.) read-all))

(defn desugar-host-expr [[op & expr :as form]]
(if (symbol? op)
(let [opname (name op)]
(cond
(= (first opname) \.) ; (.foo bar ..)
(let [[target & args] expr
args (list* (symbol (subs opname 1)) args)]
(with-meta (list '. target (if (= 1 (count args)) ;; we don't know if (.foo bar) ia
(first args) args)) ;; a method call or a field access
(meta form)))
(= (last opname) \.) ;; (class. ..)
(with-meta (list* 'new (symbol (subs opname 0 (dec (count opname)))) expr)
(meta form))
:else form))
form))

(defn macroexpand-1 [form env]
(if (seq? form)
(let [op (first form)]
(if (ana/specials op)
form
(let [v (resolve-var op env)]
(if (and (not (-> env :locals (get op))) ;; locals cannot be macros
(:macro (meta v)))
(apply v form env (rest form)) ; (m &form &env & args)
(desugar-host-expr form)))))
form))

(def e (ana/empty-env))

(defmacro ast [form]
`(binding [ana/macroexpand-1 macroexpand-1
ana/create-var ~(fn [sym env]
(doto (intern (:ns env) sym)
(reset-meta! (meta sym))))
ana/parse ana/-parse
ana/var? ~var?]
(ana/analyze '~form e)))

(defmacro string-ast [string]
`(binding [ana/macroexpand-1 macroexpand-1
ana/create-var ~(fn [sym env]
(doto (intern (:ns env) sym)
(reset-meta! (meta sym))))
ana/parse ana/-parse
ana/var? ~var?]
(ana/analyze (r/read-string ~string) e)))

(defmacro file-ast [file]
`(binding [ana/macroexpand-1 macroexpand-1
ana/create-var ~(fn [sym env]
(doto (intern (:ns env) sym)
(reset-meta! (meta sym))))
ana/parse ana/-parse
ana/var? ~var?]
(ana/analyze (read-all-file ~file) e)))

(defn find-referred [ns-body referred]
(some #(= (symbol referred) (:class %)) (nodes (string-ast ns-body))))
6 changes: 6 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(defproject clj-refactor "0.12.0"
:description "To initate a helper cider session with analyzer support for ASTs"
:profiles {:dev {:source-paths ["dev"]
:dependencies [[org.clojure/tools.analyzer "0.1.0-beta10"]
[org.clojure/tools.analyzer.jvm "0.1.0-beta10"]
[org.clojure/tools.reader "0.8.4"]]}})

0 comments on commit cdf1c16

Please sign in to comment.