Add --fix command to CLI.
If the user declines a fix, it will stop fixing the file it's looking at and go on to the next one. If they accept the fix, we have to re-run diagnostics, because fixes can change offsets and line numbers. Add tests that invoke the `fennel-ls` executable as a subprocess.
This commit is contained in:
parent
bf17771bab
commit
2b79a0955f
@ -1,6 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
### Features
|
||||
* Add `--fix` command-line argument to automatically apply lint fixes
|
||||
* Add `:legacy-multival` and `legacy-multival-case` lints, disabled by default
|
||||
* Code action "Expand macro" lets you see what a macro expands to
|
||||
* Macro expansion is shown when hovering over a macro
|
||||
* Added support for pull diagnostics, which should help with out of date lints
|
||||
|
||||
@ -131,3 +131,11 @@ fennel-ls --lint my-file.fnl f2.fnl # prints diagnostics for the files given
|
||||
|
||||
This will analyze the given files and print out all compiler errors and lints,
|
||||
without launching a server. A successful exit code indicates no problems found.
|
||||
|
||||
You can also automatically apply fixes from lints that have them:
|
||||
|
||||
```sh
|
||||
fennel-ls --fix my-file.fnl f2.fnl # applies fixes
|
||||
my-file.fnl:3:6: warning: unnecessary unary +
|
||||
Apply fix? [Y/n] Unwrap the expression
|
||||
```
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
(require :fennel)
|
||||
(local dispatch (require :fennel-ls.dispatch))
|
||||
(local json-rpc (require :fennel-ls.json-rpc))
|
||||
(local lint (require :fennel-ls.lint))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
(local files (require :fennel-ls.files))
|
||||
(local {: severity->string &as message} (require :fennel-ls.message))
|
||||
|
||||
(fn print-diagnostic [filename msg range ?severity]
|
||||
(fn print-diagnostic [filename {:message msg : range : severity}]
|
||||
(print (: "%s:%s:%s: %s: %s" :format
|
||||
filename
|
||||
;; LSP line numbers are zero-indexed, but Emacs and Vim both use
|
||||
;; 1-indexing for this.
|
||||
(if (= range message.unknown-range) "?" (+ range.start.line 1))
|
||||
(if (= range message.unknown-range) "?" range.start.character)
|
||||
(or (. severity->string ?severity) "?")
|
||||
(or (. severity->string severity) "?")
|
||||
msg)))
|
||||
|
||||
(fn initialize [server]
|
||||
@ -26,11 +28,10 @@
|
||||
{:method "window/showMessage" :params {: message}}
|
||||
(print "WARN:" message)))))
|
||||
|
||||
(λ lint [filenames]
|
||||
(λ lint-files [filenames]
|
||||
"non-interactive mode that gets executed from CLI with --lint.
|
||||
runs lints on each file, then formats and prints them"
|
||||
(let [lint (require :fennel-ls.lint)
|
||||
server (doto {} initialize)]
|
||||
(let [server (doto {} initialize)]
|
||||
(var should-err? false)
|
||||
(each [_ filename (ipairs filenames)]
|
||||
(let [uri (case filename
|
||||
@ -38,12 +39,39 @@
|
||||
_ (.. "file://" filename))
|
||||
file (files.get-by-uri server uri)]
|
||||
(lint.add-lint-diagnostics server file)
|
||||
(each [_ {: message : range : severity} (ipairs file.diagnostics)]
|
||||
(each [_ diagnostic (ipairs file.diagnostics)]
|
||||
(set should-err? true)
|
||||
(print-diagnostic filename message range severity))))
|
||||
(print-diagnostic filename diagnostic))))
|
||||
(when should-err?
|
||||
(os.exit 1))))
|
||||
|
||||
(λ apply-changes [server filename changes]
|
||||
(let [contents (with-open [f (io.open filename)] (f:read :*a))
|
||||
new (utils.apply-edits contents changes server.position-encoding)]
|
||||
(with-open [f (io.open filename :w)]
|
||||
(f:write new))))
|
||||
|
||||
(λ confirm [msg]
|
||||
(io.write msg)
|
||||
(case (io.read) (where (or "" "y" "Y" "yes")) true))
|
||||
|
||||
(λ fix-files [filenames --yes]
|
||||
(let [server (doto {} initialize)]
|
||||
(each [_ filename (ipairs filenames)]
|
||||
(let [uri (case filename
|
||||
"-" :stdin
|
||||
_ (.. "file://" filename))
|
||||
file (files.get-by-uri server uri)]
|
||||
(lint.add-lint-diagnostics server file)
|
||||
(case [(next file.diagnostics)]
|
||||
[_ {: fix &as diagnostic}]
|
||||
(let [{: title : changes} (fix)
|
||||
query (: "Apply fix? [Y/n] %s " :format title)]
|
||||
(print-diagnostic filename diagnostic)
|
||||
(when (or --yes (confirm query))
|
||||
(apply-changes server filename changes)
|
||||
(fix-files [filename] --yes))))))))
|
||||
|
||||
(λ main-loop [in out]
|
||||
(local send (partial json-rpc.write out))
|
||||
(local server {})
|
||||
@ -56,22 +84,26 @@
|
||||
|
||||
Run fennel-ls, the Fennel language server and linter.
|
||||
|
||||
--lint FILES : Run the linter on the provided files
|
||||
a single dash (-) can be used to read from stdin
|
||||
--server : Start the language server (stdio mode only)
|
||||
optional, this is the default with no arguments
|
||||
--lint FILES : Run the linter on the provided files
|
||||
a single dash (-) can be used to read from stdin
|
||||
--fix [-y] FILES : Run suggested fixes from linters on files
|
||||
--server : Start the language server (stdio mode only)
|
||||
optional, this is the default with no arguments
|
||||
|
||||
--help : Display this text
|
||||
--version : Show version")
|
||||
--help : Display this text
|
||||
--version : Show version")
|
||||
|
||||
(λ main []
|
||||
(case arg
|
||||
(where (or ["-h"] ["--help"])) (print help)
|
||||
(where (or ["-v"] ["--version"])) (print version)
|
||||
;; (where (or ["-l" & filenames] ["--lint" & filenames])) (lint filenames) ;; compile error in fennel <= 1.5.4
|
||||
(where [--lint & filenames] (or (= --lint "--lint") (= --lint "-l"))) (lint filenames)
|
||||
(where [--lint & filenames] (or (= --lint "--lint") (= --lint "-l"))) (lint-files filenames)
|
||||
(where (or ["--server"] [nil])) (main-loop (io.input)
|
||||
(io.output))
|
||||
["--fix" "-y" & filenames] (fix-files filenames true)
|
||||
["--fix" "--yes" & filenames] (fix-files filenames true)
|
||||
["--fix" & filenames] (fix-files filenames false)
|
||||
_args (do (io.stderr:write help)
|
||||
(io.stderr:write "\n")
|
||||
(os.exit 1))))
|
||||
|
||||
@ -139,9 +139,7 @@ These functions are all pure functions, which makes me happy."
|
||||
text)))
|
||||
|
||||
(λ apply-edits [initial-text edits encoding]
|
||||
"Takes a list of Language-Server-Protocol `TextEdit` or `AnnotatedTextEdit` and applies them to a piece of text.
|
||||
|
||||
WARNING: this is only used in the test code, not in the real language server"
|
||||
"Takes a list of Language-Server-Protocol `TextEdit` or `AnnotatedTextEdit` and applies them to a piece of text."
|
||||
(accumulate
|
||||
[contents initial-text
|
||||
_ edit (ipairs edits)]
|
||||
|
||||
44
test/cli.fnl
44
test/cli.fnl
@ -1,19 +1,35 @@
|
||||
(local faith (require :faith))
|
||||
|
||||
;; TODO refactor fennel-ls.check to avoid running end-to-end
|
||||
;; (but keep/write at least some end-to-end tests)
|
||||
(fn test-lint []
|
||||
(let [input-file-name (os.tmpname)]
|
||||
(doto (io.open input-file-name :w)
|
||||
(: :write "(local x 1)")
|
||||
;; alas, no io.popen2
|
||||
(macro with-temp-form [[filename form
|
||||
out cmd] & body]
|
||||
`(let [,filename (os.tmpname)]
|
||||
(doto (assert (io.open ,filename :w))
|
||||
(: :write ,(view form))
|
||||
(: :close))
|
||||
(let [output-file (io.popen (.. "./fennel-ls --lint "
|
||||
input-file-name)
|
||||
:r)
|
||||
contents (output-file:read :*a)]
|
||||
(os.remove input-file-name)
|
||||
(faith.= (.. input-file-name ":1:7: warning: unused definition: x\n")
|
||||
contents))
|
||||
(let [pipe# (io.popen (.. ,cmd " " ,filename))
|
||||
,out (pipe#:read :*a)]
|
||||
,body
|
||||
(os.remove ,filename))
|
||||
nil))
|
||||
|
||||
{: test-lint}
|
||||
(fn test-lint []
|
||||
(with-temp-form [f (local x 1)
|
||||
out "./fennel-ls --lint"]
|
||||
(faith.= (.. f ":1:7: warning: unused definition: x\n") out)))
|
||||
|
||||
(fn test-fix []
|
||||
(with-temp-form [f (local x 1)
|
||||
out "./fennel-ls --fix --yes"]
|
||||
(faith.= "(local _x 1)" (with-open [file (assert (io.open f))]
|
||||
(file:read :*a)) out))
|
||||
(with-temp-form [f [(let [(a b) (values 1 (+ 2))] (+ a b))
|
||||
[(do (print "done doing all the fun things we did!"))]]
|
||||
out "./fennel-ls --fix --yes"]
|
||||
(faith.= "[(let [[a b] [(values 1 2)]] (+ a b))
|
||||
[(print \"done doing all the fun things we did!\")]]"
|
||||
(with-open [file (assert (io.open f))]
|
||||
(file:read :*a)) out)))
|
||||
|
||||
{: test-lint
|
||||
: test-fix}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user