Phil Hagelberg: 1 Add unnecessary-tset, unnecessary-do, and redundant-do lints. 3 files changed, 94 insertions(+), 6 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~xerool/fennel-ls/patches/55282/mbox | git am -3Learn more about email & git
"Redundant" means that it's a `do` in a context where there's already an implicit `do`, as defined by fennel.syntax indicating body-form? is true. Unnecessary means it only has one argument. --- src/fennel-ls/config.fnl | 3 +++ src/fennel-ls/lint.fnl | 47 ++++++++++++++++++++++++++++++++++++- test/lint.fnl | 50 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/fennel-ls/config.fnl b/src/fennel-ls/config.fnl index 9ede9e3..64f6df0 100644 --- a/src/fennel-ls/config.fnl +++ b/src/fennel-ls/config.fnl @@ -23,6 +23,9 @@ There are no global settings. They're all stored in the `server` object. :lints {:unused-definition (option true) :unknown-module-field (option true) :unnecessary-method (option true) + :unnecessary-tset (option true) + :unnecessary-do (option true) + :redundant-do (option true) :match-should-case (option true) :bad-unpack (option true) :var-never-set (option true) diff --git a/src/fennel-ls/lint.fnl b/src/fennel-ls/lint.fnl index f154f15..580cbcf 100644 --- a/src/fennel-ls/lint.fnl +++ b/src/fennel-ls/lint.fnl @@ -2,7 +2,7 @@ Provides the function (check server file), which goes through a file and mutates the `file.diagnostics` field, filling it with diagnostics." -(local {: sym? : list? : table? : view} (require :fennel)) +(local {: sym? : list? : table? : view &as fennel} (require :fennel)) (local analyzer (require :fennel-ls.analyzer)) (local message (require :fennel-ls.message)) (local utils (require :fennel-ls.utils)) @@ -81,6 +81,48 @@ the `file.diagnostics` field, filling it with diagnostics." :code 303 :codeDescription "unnecessary-method"})))) +(λ unnecessary-tset [server file head call] + (if (and (sym? head :tset) (sym? (. call 2)) + (= :string (type (. call 3))) (. file.lexical call)) + (diagnostic {:range (message.ast->range server file call) + :message (string.format "unnecessary %s" head) + :severity message.severity.WARN + :code 309 + :codeDescription "unnecessary-tset"} + #[{:range (message.ast->range server file call) + :newText (string.format "(set %s.%s %s)" + (. call 2) (. call 3) + (view (. call 4)))}]))) + +(λ unnecessary-do-values [server file head call] + (if (and (or (sym? head :do) (sym? head :values)) + (= nil (. call 3)) (. file.lexical call)) + (diagnostic {:range (message.ast->range server file call) + :message (string.format "unnecessary %s" head) + :severity message.severity.WARN + :code 310 + :codeDescription "unnecessary-do-values"} + #[{:range (message.ast->range server file call) + :newText (view (. call 2))}]))) + +(local implicit-do-forms (collect [form {: body-form?} (pairs (fennel.syntax))] + (values form body-form?))) + +(λ redundant-do [server file head call] + (let [last-body (. call (length call))] + (if (and (. implicit-do-forms (tostring head)) (. file.lexical call) + (list? last-body) (sym? (. last-body 1) :do)) + (diagnostic {:range (message.ast->range server file last-body) + :message "redundant do" + :severity message.severity.WARN + :code 311 + :codeDescription "redundant-do"} + #[{:range (message.ast->range server file last-body) + :newText (table.concat + (fcollect [i 2 (length last-body)] + (view (. last-body i))) + " ")}])))) + (λ bad-unpack [server file op call] "an unpack call leading into an operator" (let [last-item (. call (length call))] @@ -185,6 +227,9 @@ the `file.diagnostics` field, filling it with diagnostics." (when head (if lints.bad-unpack (table.insert diagnostics (bad-unpack server file head call))) (if lints.unnecessary-method (table.insert diagnostics (unnecessary-method server file head call))) + (if lints.unnecessary-do (table.insert diagnostics (unnecessary-do-values server file head call))) + (if lints.unnecessary-tset (table.insert diagnostics (unnecessary-tset server file head call))) + (if lints.redundant-do (table.insert diagnostics (redundant-do server file head call))) (if lints.op-with-no-arguments (table.insert diagnostics (op-with-no-arguments server file head call))) ;; argument lints diff --git a/test/lint.fnl b/test/lint.fnl index 333f25e..f627a13 100644 --- a/test/lint.fnl +++ b/test/lint.fnl @@ -177,6 +177,47 @@ [] [{:code 307}]) nil) +(fn test-unnecessary-tset [] + ;; valid, if you're targeting older Fennels + (check "(local [tbl key] [{} :k]) (tset tbl key 249)" [] [{}]) + ;; never a good use of tset + (check "(local tbl {}) (tset tbl :key 9)" + [{:code 309 + :codeDescription "unnecessary-tset" + :message "unnecessary tset" + :range {:start {:character 15 :line 0} + :end {:character 32 :line 0}}}] [])) + +(fn test-unnecessary-do [] + ;; multi-arg do + (check "(do (print :x) 11)" [] [{}]) + ;; unnecessary do + (check "(do 9)" [{:message "unnecessary do" + :code 310 + :codeDescription "unnecessary-do-values" + :range {:start {:character 0 :line 0} + :end {:character 6 :line 0}}}] []) + ;; unnecessary values + (check "(print :hey (values :lol))" + [{:code 310 + :codeDescription "unnecessary-do-values" + :message "unnecessary values" + :range {:start {:character 12 :line 0} + :end {:character 25 :line 0}}}] + [])) + +(fn test-redundant-do [] + ;; good do + (check "(case 134 x (do (print :x x) 11))" [] [{}]) + ;; unnecessary one + (set _G.dbg true) + (check "(let [x 29] (do (print 9) x))" + [{:code 311 + :codeDescription "redundant-do" + :message "redundant do" + :range {:start {:character 12 :line 0} + :end {:character 28 :line 0}}}] [])) + (fn test-match-should-case [] ;; OK: most basic pinning (check "(let [x 99] (match 99 x :yep!))" [] [{}]) @@ -207,16 +248,12 @@ :end {:character 6 :line 0}}}] [])) ;; TODO lints: -;; unnecessary (do) in body position ;; duplicate keys in kv table -;; (tset <sym> <str>) --> (set <sym>.<str>) -;; (tset <sym> <any>) --> (set (. <sym> <any>)) +;; (tset <sym> <any>) --> (set (. <sym> <any>)) (might be wanted for compat?) ;; {&as x} and [&as x] pattern with no other matches ;; Unused variables / fields (maybe difficult) ;; discarding results to various calls, such as unpack, values, etc -;; unnecessary `do`/`values` with only one inner form ;; `pairs` or `ipairs` call in a (for) binding table -;; mark when unification is happening on a `match` pattern (may be difficult) ;; steal as many lints as possible from cargo ;; unnecessary parens around single multival destructure @@ -226,6 +263,9 @@ : test-ampersand : test-unknown-module-field : test-unnecessary-colon + : test-unnecessary-tset + : test-unnecessary-do + : test-redundant-do : test-unset-var : test-match-should-case : test-unpack-into-op -- 2.39.5