diff --git a/src/fennel-ls/config.fnl b/src/fennel-ls/config.fnl index 83096da..0db15f7 100644 --- a/src/fennel-ls/config.fnl +++ b/src/fennel-ls/config.fnl @@ -35,7 +35,8 @@ There are no global settings. They're all stored in the `server` object. :var-never-set (option true) :op-with-no-arguments (option true) :multival-in-middle-of-call (option true) - :no-decreasing-comparison (option false)} + :no-decreasing-comparison (option false) + :empty-let (option true)} :libraries (option {}) :extra-globals (option "")}) diff --git a/src/fennel-ls/lint.fnl b/src/fennel-ls/lint.fnl index 7a57dd9..2d2f816 100644 --- a/src/fennel-ls/lint.fnl +++ b/src/fennel-ls/lint.fnl @@ -242,6 +242,22 @@ the `file.diagnostics` field, filling it with diagnostics." :severity message.severity.WARN :code :inline-unpack})) +(λ empty-let [server file _ call] + (case call + (where [let* binding] + (. file.lexical call) + (sym? let* :let) + (fennel.sequence? binding) + (= 0 (length binding))) + (diagnostic {:range (message.ast->range server file binding) + :message "use do instead of let with no bindings" + :severity message.severity.WARN + :code :empty-let} + #[(let [{: start} (message.ast->range server file let*) + {: end} (message.ast->range server file binding)] + {:range {: start : end} + :newText "do"})]))) + (λ add-lint-diagnostics [server file] "fill up the file.diagnostics table with linting things" (let [lints server.configuration.lints @@ -272,6 +288,8 @@ the `file.diagnostics` field, filling it with diagnostics." (table.insert diagnostics (op-with-no-arguments server file head call))) (when lints.no-decreasing-comparison (table.insert diagnostics (no-decreasing-comparison server file head call))) + (when lints.empty-let + (table.insert diagnostics (empty-let server file head call))) ;; argument lints ;; every argument to a special or a function call diff --git a/test/cli.fnl b/test/cli.fnl index ed7f279..035a48a 100644 --- a/test/cli.fnl +++ b/test/cli.fnl @@ -9,10 +9,11 @@ (: :close)) (let [output-file (io.popen (.. "./fennel-ls --lint " input-file-name) - :r)] + :r) + contents (output-file:read :*a)] + (os.remove input-file-name) (faith.= (.. input-file-name ":1:7: warning: unused definition: x\n") - (output-file:read :*a)) - (os.remove input-file-name)) + contents)) nil)) {: test-lint} diff --git a/test/lint.fnl b/test/lint.fnl index 822e850..e67ae8d 100644 --- a/test/lint.fnl +++ b/test/lint.fnl @@ -7,18 +7,19 @@ (accumulate [result nil i d (ipairs diagnostics) &until result] - (if (and (or (= e.message nil) - (if (= (type e.message) "function") - (e.message d.message) - (= e.message d.message))) - (or (= e.code nil) - (= e.code d.code)) - (or (= e.range nil) - (and (= e.range.start.line d.range.start.line) - (= e.range.start.character d.range.start.character) - (= e.range.end.line d.range.end.line) - (= e.range.end.character d.range.end.character)))) - i))) + (let [d (or d.self d)] + (if (and (or (= e.message nil) + (if (= (type e.message) "function") + (e.message d.message) + (= e.message d.message))) + (or (= e.code nil) + (= e.code d.code)) + (or (= e.range nil) + (and (= e.range.start.line d.range.start.line) + (= e.range.start.character d.range.start.character) + (= e.range.end.line d.range.end.line) + (= e.range.end.character d.range.end.character)))) + i)))) (fn check [file-contents expected ?unexpected] (let [{: diagnostics} (create-client file-contents)] @@ -32,8 +33,8 @@ (let [i (find diagnostics e)] (faith.is i (.. "No lint matching " (view e) "\n" "from: " (view file-contents) "\n" - (view diagnostics {:empty-as-sequence? true - :escape-newlines? true}))) + "possible matches: " (view diagnostics {:empty-as-sequence? true + :escape-newlines? true}))) (table.remove diagnostics i))))) (fn assert-ok [file-contents] @@ -257,12 +258,21 @@ (assert-ok "(and false 1)") (assert-ok "(and nil 1)") (check "(and)" - {:message "write true instead of (and)" - :code :op-with-no-arguments - :range {:start {:characer 0 :line 0} - :end {:character 5 :line 0}}}) + [{:message "write true instead of (and)" + :code :op-with-no-arguments + :range {:start {:character 0 :line 0} + :end {:character 5 :line 0}}}]) nil) +(fn test-empty-let [] + (assert-ok "(let [x 1] x)") + (check "(let [] print)" + [{:message "use do instead of let with no bindings" + :code :empty-let + :range {:start {:character 5 :line 0} + :end {:character 7 :line 0}}}]) + (assert-ok "(-> [] (let print))")) + ;; TODO lints: ;; duplicate keys in kv table ;; (tset ) --> (set (. )) (might be wanted for compat?) @@ -286,4 +296,5 @@ : test-match-should-case : test-unpack-into-op : test-unpack-in-middle - : test-op-with-no-arguments} + : test-op-with-no-arguments + : test-empty-let} diff --git a/test/utils/client.fnl b/test/utils/client.fnl index d1dc210..95614f6 100644 --- a/test/utils/client.fnl +++ b/test/utils/client.fnl @@ -30,6 +30,11 @@ {: position :textDocument {:uri file}}))) +(fn completion-item-resolve [self completion-item] + (dispatch.handle* self.server + (message.create-request (next-id! self) :completionItem/resolve + completion-item))) + (fn definition [self file position] (dispatch.handle* self.server (message.create-request (next-id! self) :textDocument/definition @@ -87,6 +92,7 @@ {:__index {: open-file! : pretend-this-file-exists! : completion + : completion-item-resolve : definition : hover : references