├── .gitignore ├── glide.yaml ├── .travis.yml ├── glide.lock ├── install ├── plugin └── clojure-check.vim ├── README.adoc └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /output 3 | /clojure-check 4 | /bin 5 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/google/uuid 4 | - package: github.com/jackpal/bencode-go 5 | - package: gopkg.in/edn.v1 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | install: 3 | - go get github.com/mitchellh/gox 4 | - curl https://glide.sh/get | sh 5 | script: 6 | - glide install 7 | - gox -output "$TRAVIS_BUILD_DIR/clojure-check_{{.OS}}_{{.Arch}}" 8 | deploy: 9 | provider: releases 10 | api_key: "$GITHUB_TOKEN" 11 | file_glob: true 12 | file: clojure-check* 13 | skip_cleanup: true 14 | on: 15 | tags: true 16 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 9b2bc04253811a8ddd5d11329338fd3a4f6590f8ea36490cae1f893d5fdcfb14 2 | updated: 2017-02-18T23:48:07.405145791Z 3 | imports: 4 | - name: github.com/google/uuid 5 | version: 064e2069ce9c359c118179501254f67d7d37ba24 6 | - name: github.com/jackpal/bencode-go 7 | version: 8b8d9a9b37fcd43f365439810d725081b114af68 8 | - name: gopkg.in/edn.v1 9 | version: 470b0ab438bbcd34ad4b5cab3d1eb1c13d69d6b9 10 | testImports: [] 11 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version=0.2 4 | cd "$(dirname "${BASH_SOURCE[0]}")" 5 | check_base="$(pwd)" 6 | bin_name="clojure-check-v$version" 7 | bin_dir="$check_base/bin" 8 | target_bin="$bin_dir/$bin_name" 9 | 10 | try_wget() { 11 | command -v wget > /dev/null && wget -O $bin_name $1 12 | } 13 | 14 | try_curl() { 15 | command -v curl > /dev/null && curl -fL $1 -o $bin_name 16 | } 17 | 18 | download() { 19 | if ! [ -x $target_bin ]; then 20 | rm -rf $bin_dir/* 21 | local url="https://github.com/SevereOverfl0w/clojure-check/releases/download/v$version/$1" 22 | try_curl $url || try_wget $url 23 | chmod +x $target_bin 24 | fi 25 | } 26 | 27 | # Create bin dir 28 | mkdir -p $bin_dir && cd $bin_dir 29 | 30 | # Try to download binary executable 31 | archi=$(uname -sm) 32 | binary_available=1 33 | case "$archi" in 34 | Darwin\ *64) download clojure-check_darwin_${binary_arch:-amd64} ;; 35 | Darwin\ *86) download clojure-check_darwin_${binary_arch:-386} ;; 36 | Linux\ *64) download clojure-check_linux_${binary_arch:-amd64} ;; 37 | Linux\ *86) download clojure-check_linux_${binary_arch:-386} ;; 38 | Linux\ armv5*) download clojure-check_linux_${binary_arch:-arm5} ;; 39 | Linux\ armv6*) download clojure-check_linux_${binary_arch:-arm6} ;; 40 | Linux\ armv7*) download clojure-check_linux_${binary_arch:-arm7} ;; 41 | Linux\ armv8*) download clojure-check_linux_${binary_arch:-arm8} ;; 42 | FreeBSD\ *64) download clojure-check_freebsd_${binary_arch:-amd64} ;; 43 | FreeBSD\ *86) download clojure-check_freebsd_${binary_arch:-386} ;; 44 | OpenBSD\ *64) download clojure-check_openbsd_${binary_arch:-amd64} ;; 45 | OpenBSD\ *86) download clojure-check_openbsd_${binary_arch:-386} ;; 46 | *) binary_available=0 ;; 47 | esac 48 | 49 | if [ $binary_available -eq 0 ]; then 50 | echo "No prebuilt binary for $archi ..." 51 | fi 52 | -------------------------------------------------------------------------------- /plugin/clojure-check.vim: -------------------------------------------------------------------------------- 1 | " This Source Code Form is subject to the terms of the Mozilla Public 2 | " License, v. 2.0. If a copy of the MPL was not distributed with this 3 | " file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | if exists('g:loaded_clojure_check') 6 | finish 7 | endif 8 | let g:loaded_clojure_check = 1 9 | 10 | let s:check_version = '0.2' 11 | let s:base_dir = expand(':h:h') 12 | let g:clojure_check_bin = s:base_dir.'/bin/clojure-check-v'.s:check_version 13 | 14 | function! s:ClojureHost() 15 | return fireplace#client().connection.transport.host 16 | endfunction 17 | 18 | function! s:ClojurePort() 19 | return fireplace#client().connection.transport.port 20 | endfunction 21 | 22 | function! s:ClojureHostPort() 23 | if exists("g:acid_loaded") 24 | let host_port = AcidGetUrl() 25 | if string(host_port) == "v:null" 26 | throw 'Acid: No repl connection' 27 | endif 28 | else 29 | let host_port = [s:ClojureHost(), s:ClojurePort()] 30 | endif 31 | return join(host_port, ":") 32 | endfunction 33 | 34 | function! s:ClojureNs(buffer) 35 | if exists("g:acid_loaded") 36 | return AcidGetNs() 37 | else 38 | return fireplace#ns(a:buffer) 39 | endif 40 | endfunction 41 | 42 | function! ClojureCheckArgs(buffer) 43 | try 44 | return ['-nrepl', s:ClojureHostPort(), '-namespace', s:ClojureNs(a:buffer)] 45 | catch /Fireplace\|Acid/ 46 | return [] 47 | endtry 48 | endfunction 49 | 50 | function! ClojureCheck(buffer) 51 | let clj_args = ClojureCheckArgs(a:buffer) 52 | if len(clj_args) > 0 53 | return g:clojure_check_bin.' '.join(clj_args + ['-file', '-'], ' ') 54 | else 55 | return '' 56 | endif 57 | endfunction 58 | 59 | try 60 | call ale#linter#Define('clojure', { 61 | \ 'name': 'clojure_check', 62 | \ 'executable': g:clojure_check_bin, 63 | \ 'command_callback': 'ClojureCheck', 64 | \ 'callback': 'ale#handlers#unix#HandleAsError', 65 | \}) 66 | catch /E117/ 67 | endtry 68 | 69 | let g:neomake_clojure_check_maker = { 70 | \ 'exe': g:clojure_check_bin, 71 | \ 'errorformat': '%f:%l:%c: %m', 72 | \ } 73 | 74 | function! g:neomake_clojure_check_maker.args() 75 | return ClojureCheckArgs(bufnr('%'))+ ['-file'] 76 | endfunction 77 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Clojure Check: A command-line interface to checkers via nREPL 2 | 3 | == Rationale 4 | 5 | This is a quicker alternative to `lein eastwood` or `lein kibit` when you already have an nREPL running. The main driver is to be able to refer this from Vim for tools like https://github.com/w0rp/ale[ALE] 6 | 7 | == Usage 8 | 9 | === Downloading 10 | 11 | There are pre-built binaries available in the https://github.com/SevereOverfl0w/clojure-check/releases[releases]. I sign all releases, so look for the Verified sign! 12 | 13 | === Building 14 | 15 | I use https://github.com/Masterminds/glide[glide] to build this project. 16 | 17 | NOTE: I had to run `git config --global http.https://gopkg.in.followRedirects true` with the latest git. 18 | 19 | [source,shell] 20 | ---- 21 | $ glide install 22 | $ go build <1> 23 | ---- 24 | <1> Creates ./clojure-check 25 | 26 | === CLI usage 27 | 28 | You can get output from eastwood & kibit on a running nREPL via 29 | 30 | [source,shell] 31 | ---- 32 | $ ./output -nrepl localhost:33999 -namespace app.website -file - < src/app/website.clj<1> 33 | $ ./output -nrepl localhost:33999 -namespace app.website -file src/app/website.clj 34 | ---- 35 | <1> Read from Stdin 36 | 37 | === REPL setup 38 | 39 | [source,clojure] 40 | .~/.lein/profiles.clj 41 | ---- 42 | {:user 43 | {:dependencies 44 | [[jonase/eastwood "0.2.3" :exclusions [org.clojure/clojure]] 45 | [jonase/kibit "0.1.3" :exclusions [org.clojure/clojure]]]}} 46 | ---- 47 | 48 | [source,clojure] 49 | .~/.boot/profile.boot 50 | ---- 51 | ;; OR merge this with your cider task 52 | (deftask linters "Linter profile" 53 | [] 54 | (require 'boot.repl) 55 | (swap! @(resolve 'boot.repl/*default-dependencies*) 56 | concat '[[jonase/eastwood "0.2.3" :exclusions [org.clojure/clojure]] 57 | [jonase/kibit "0.1.3" :exclusions [org.clojure/clojure]]) 58 | identity) 59 | ---- 60 | 61 | == Editor integration 62 | 63 | Inspired by http://ddg.gg/[fzf] I have included a plugin folder in this repo which allows easy integration with Vim. I welcome similar PRs for other editors. 64 | 65 | ==== Vim - ALE 66 | 67 | There is ALE integration available in this repo. It depends on Fireplace to find connection details. 68 | 69 | ---- 70 | Plug 'w0rp/ale' 71 | Plug 'SevereOverfl0w/clojure-check', {'do': './install'} 72 | ---- 73 | 74 | ==== Vim - Neomake 75 | 76 | There is Neomake integration available in this repo. It depends on Fireplace to find connection details. 77 | 78 | ---- 79 | Plug 'neomake/neomake' 80 | Plug 'SevereOverfl0w/clojure-check', {'do': './install'} 81 | 82 | let g:neomake_clojure_enabled_makers = ['check'] 83 | ---- 84 | 85 | ==== Vim - makeprg 86 | 87 | It's easy to integrate clojure-check with `:make` in Vim. 88 | 89 | ---- 90 | Plug 'SevereOverfl0w/clojure-check', {'do': './install'} 91 | ---- 92 | 93 | As parameters are required for making with this CLI, I suggest you also include the `ClojureMake` wrapper for `:make` to automatically insert those args, or any similar usage of the same thing. 94 | 95 | .ftplugin/clojure.vim 96 | ---- 97 | let &makeprg=g:clojure_check_bin.' $* -file %' 98 | command! ClojureMake :execute ':make '.join(ClojureCheckArgs(bufnr('%'))) 99 | ---- 100 | 101 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/uuid" 6 | bencode "github.com/jackpal/bencode-go" 7 | "gopkg.in/edn.v1" 8 | "io/ioutil" 9 | "net" 10 | "os" 11 | "flag" 12 | "regexp" 13 | ) 14 | 15 | func stringInSlice(a string, list []string) bool { 16 | for _, b := range list { 17 | if b == a { 18 | return true 19 | } 20 | } 21 | return false 22 | } 23 | 24 | type Response struct { 25 | Ex string 26 | Value string 27 | Status []string 28 | Id string 29 | Session string 30 | Out string 31 | } 32 | 33 | type EastwoodArgs struct { 34 | Namespaces []edn.Symbol `edn:",omitempty"` 35 | } 36 | 37 | func printmsgid(conn net.Conn, msgid string) { 38 | for { 39 | result := Response{} 40 | err := bencode.Unmarshal(conn, &result) 41 | if err != nil { 42 | fmt.Println(err) 43 | return 44 | } 45 | if result.Id != msgid { 46 | fmt.Println("Skipping this message id") 47 | continue 48 | } 49 | if result.Ex != "" { 50 | fmt.Println(result.Ex) 51 | } 52 | 53 | if result.Out != "" { 54 | matched, _ := regexp.MatchString(".+:.+\n", result.Out) 55 | xmatch, _ := regexp.MatchString("==.+\n", result.Out) 56 | if matched && !xmatch { 57 | fmt.Print(result.Out) 58 | } 59 | } 60 | 61 | // if result.Value != "" { 62 | // fmt.Println("value: ") 63 | // fmt.Println(result.Value) 64 | // } 65 | 66 | if len(result.Status) > 0 { 67 | return 68 | // if stringInSlice("done", result.Status) { 69 | // return 70 | // } 71 | } 72 | } 73 | } 74 | 75 | func eastwood(args []string, conn net.Conn) { 76 | ns := make([]edn.Symbol, len(args)) 77 | 78 | for index, element := range args { 79 | ns[index] = edn.Symbol(element) 80 | } 81 | 82 | x := EastwoodArgs{ns} 83 | b, err := edn.Marshal(x) 84 | code := fmt.Sprintf(`(do (require 'eastwood.lint) (eastwood.lint/eastwood '%v))`, string(b)) 85 | 86 | msguuid, _ := uuid.NewRandom() 87 | msgid := msguuid.String() 88 | 89 | instruction := map[string]interface{}{ 90 | "op": "eval", 91 | "code": code, 92 | "id": msgid, 93 | } 94 | err = bencode.Marshal(conn, instruction) 95 | 96 | if err != nil { 97 | fmt.Println(err) 98 | return 99 | } 100 | 101 | printmsgid(conn, msgid) 102 | } 103 | 104 | func kibit(input string, file bool, conn net.Conn) { 105 | var reader string 106 | 107 | reporter := `(fn [{:keys [file line expr alt]}] (printf "%s:%s:0: Consider using: %s Instead of %s\n" file line (pr-str alt) (pr-str expr)))` 108 | 109 | if file { 110 | escapedFileBin, err := edn.Marshal(input) 111 | 112 | if err != nil { 113 | fmt.Println(err) 114 | return 115 | } 116 | 117 | escapedFile := string(escapedFileBin) 118 | 119 | reader = fmt.Sprintf(`(kibit.check/check-file (java.io.File. %v) :reporter %v)`, escapedFile, reporter) 120 | } else { 121 | reader = fmt.Sprintf(`(run! %v (kibit.check/check-reader (java.io.StringReader. %v)))`, reporter, input) 122 | } 123 | 124 | code := fmt.Sprintf(`(do (require 'kibit.check) %v)`, reader) 125 | 126 | msguuid, _ := uuid.NewRandom() 127 | msgid := msguuid.String() 128 | 129 | instruction := map[string]interface{}{ 130 | "op": "eval", 131 | "code": code, 132 | "id": msgid, 133 | } 134 | err := bencode.Marshal(conn, instruction) 135 | 136 | if err != nil { 137 | fmt.Println(err) 138 | return 139 | } 140 | 141 | for { 142 | result := Response{} 143 | err := bencode.Unmarshal(conn, &result) 144 | if err != nil { 145 | fmt.Println(err) 146 | return 147 | } 148 | if result.Id != msgid { 149 | continue 150 | } 151 | if result.Ex != "" { 152 | fmt.Println(result.Ex) 153 | } 154 | 155 | if result.Out != "" { 156 | fmt.Print(result.Out) 157 | } 158 | 159 | if len(result.Status) > 0 { 160 | return 161 | } 162 | } 163 | } 164 | 165 | type namespaceFlags []string 166 | 167 | func (i *namespaceFlags) String() string { 168 | return "my string representation" 169 | } 170 | 171 | func (i *namespaceFlags) Set(value string) error { 172 | *i = append(*i, value) 173 | return nil 174 | } 175 | 176 | func main() { 177 | var namespaces namespaceFlags 178 | var file string 179 | var nrepl string 180 | flag.Var(&namespaces, "namespace", "Namespace to lint. Can be repeated multiple times") 181 | flag.StringVar(&file, "file", "-", "File to lint via kibit. If - will be read from stdin. Default is -") 182 | flag.StringVar(&nrepl, "nrepl", "", "nREPL connection details in form of host:port. Required.") 183 | flag.Parse() 184 | 185 | if nrepl == "" { 186 | fmt.Println("nrepl is a required parameter") 187 | return 188 | } 189 | 190 | conn, err := net.Dial("tcp", nrepl) 191 | if err != nil { 192 | fmt.Println(err) 193 | return 194 | } 195 | defer conn.Close() 196 | 197 | eastwood(namespaces, conn) 198 | 199 | if file == "-" { 200 | source_file_bytes, _ := ioutil.ReadAll(os.Stdin) 201 | b, _ := edn.Marshal(string(source_file_bytes)) 202 | kibit(string(b), false, conn) 203 | } else { 204 | kibit(file, true, conn) 205 | } 206 | } 207 | --------------------------------------------------------------------------------