From 5143310b904319c662326af9a1810170b5722020 Mon Sep 17 00:00:00 2001 From: XeroOl Date: Fri, 11 Jul 2025 19:16:06 -0500 Subject: [PATCH] introduce mismatched-argument-count lint --- src/fennel-ls/analyzer.fnl | 10 +++---- src/fennel-ls/files.fnl | 2 +- src/fennel-ls/json-rpc.fnl | 2 +- src/fennel-ls/lint.fnl | 53 ++++++++++++++++++++++++++++++++------ test/lint.fnl | 6 ++--- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/fennel-ls/analyzer.fnl b/src/fennel-ls/analyzer.fnl index 32e8230..0156477 100644 --- a/src/fennel-ls/analyzer.fnl +++ b/src/fennel-ls/analyzer.fnl @@ -169,7 +169,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find (sym? head :λ) (sym? head :hashfn)))) (search-multival server file (. definition (length definition)) stack multival opts) - result result + result_ {:indeterminate true} _ (case (docs.get-builtin server (tostring (. call 1))) {:metadata metadata_} {:indeterminate true}))))))) @@ -281,19 +281,19 @@ initialization-opts: {:stack ?list[ast] _ child (ipairs ast) &until result] (if (contains? child byte) - (recurse child byte))) + (recurse child))) (and (not (sym? ast)) (not (varg? ast))) (accumulate [(result _parent) nil key value (pairs ast) &until result] (if (contains? key byte) - (recurse key byte) + (recurse key) (contains? value byte) - (recurse value byte))))))) + (recurse value))))))) (values (accumulate [result nil _ top-level-form (ipairs ast) &until result] (if (contains? top-level-form byte) - (recurse top-level-form byte))) + (recurse top-level-form))) (fcollect [i 1 (length parents)] (. parents (- (length parents) i -1))))) diff --git a/src/fennel-ls/files.fnl b/src/fennel-ls/files.fnl index 67cd0a1..1cca814 100644 --- a/src/fennel-ls/files.fnl +++ b/src/fennel-ls/files.fnl @@ -38,7 +38,7 @@ in the \"server\" object." (or (get-by-uri server uri) ;; if the cached uri isn't found, clear the cache and try again (do (tset modules module nil) - (get-by-module server module))) + (get-by-module server module macro?))) nil (case (searcher.lookup server module macro?) uri diff --git a/src/fennel-ls/json-rpc.fnl b/src/fennel-ls/json-rpc.fnl index 921de80..817e2e0 100644 --- a/src/fennel-ls/json-rpc.fnl +++ b/src/fennel-ls/json-rpc.fnl @@ -23,7 +23,7 @@ Luckily, I'm testing with Neovim, so I can pretend these problems don't exist fo "" header ;; base case. empty line marks end of header line (let [(k v) (line:match "^(.-): (.-)$")] (if (not (and k v)) - (error "fennel-ls encountered a malformed json-rpc header: \"" line "\"")) + (error (.. "fennel-ls encountered a malformed json-rpc header: \"" line "\""))) (tset header k v) (read-header in header)))))) diff --git a/src/fennel-ls/lint.fnl b/src/fennel-ls/lint.fnl index 194de7c..abf1831 100644 --- a/src/fennel-ls/lint.fnl +++ b/src/fennel-ls/lint.fnl @@ -8,6 +8,7 @@ the `file.diagnostics` field, filling it with diagnostics." (local analyzer (require :fennel-ls.analyzer)) (local message (require :fennel-ls.message)) (local utils (require :fennel-ls.utils)) +(local navigate (require :fennel-ls.navigate)) (local dkjson (require :dkjson)) (local lints {:definition [] @@ -125,7 +126,7 @@ the `file.diagnostics` field, filling it with diagnostics." (. redundant-wrappers (tostring (. ast 1))) (= (length ast) 2)) {:range (message.ast->range server file ast) - :message (.. "unnecessary " (tostring (. ast 1))) + :message (.. "unnecessary unary " (tostring (. ast 1))) :severity message.severity.WARN :fix #{:title "Unwrap the expression" :changes [{:range (message.ast->range server file ast) @@ -247,14 +248,14 @@ the `file.diagnostics` field, filling it with diagnostics." (add-lint :inline-unpack {:type [:function-call :special-call] - :impl (fn [server file call] + :impl (fn [server file ast] "generally, values and unpack are signs that the user is trying to do something with multiple values. However, multiple values will get \"adjusted\" to one value if they don't come at the end of the call." - (faccumulate [f nil index 2 (length call) &until f] - (let [arg (. call index)] - (if (and (or (op? (. call 1)) (not (special? (. call 1)))) - (not= index (length call)) + (faccumulate [f nil index 2 (length ast) &until f] + (let [arg (. ast index)] + (if (and (or (op? (. ast 1)) (not (special? (. ast 1)))) + (not= index (length ast)) (list? arg) (or (sym? (. arg 1) :values) (sym? (. arg 1) :unpack) @@ -267,8 +268,8 @@ the `file.diagnostics` field, filling it with diagnostics." (add-lint :empty-let {:type :special-call - :impl (fn [server file call] - (case call + :impl (fn [server file ast] + (case ast (where [let* binding] (sym? let* :let) (fennel.sequence? binding) @@ -282,6 +283,42 @@ the `file.diagnostics` field, filling it with diagnostics." {:range {: start : end} :newText "do"})]}}))}) +(fn possibly-multival? [ast] + (or (fennel.list? ast) + (fennel.varg? ast))) + +(add-lint :mismatched-argument-count + {:type [:function-call :special-call :macro-call] + :disabled true + :impl (fn [server file ast] + (case (analyzer.search server file (. ast 1) {} {}) + {:indeterminate nil &as result} + (case (?. (navigate.getmetadata server result) :fnl/arglist) + signature + (let [number-of-args (- (length ast) 1) + passes-extra-args (and (not= 1 (length ast)) (possibly-multival? (. ast (length ast)))) + (number-of-params accepts-extra-params) (faccumulate [(last-required-argument vararg) nil + i (length signature) 1 -1] + (let [s (tostring (. signature i)) + m (or (= s "...") (= s "&"))] + (values + (if m + nil + last-required-argument + last-required-argument + (not= (string.sub s 1 1) "?") + i) + (or vararg m)))) + number-of-params (or number-of-params 0)] + (if (and (< number-of-args number-of-params) (not passes-extra-args)) + {:range (message.ast->range server file ast) + :message (.. "not enough args. my analysis of the signature says you need at least " number-of-params " arguments but I only see " number-of-args) + :severity message.severity.WARN} + (and (< (length signature) number-of-args) (not accepts-extra-params)) + {:range (message.ast->range server file ast) + :message (.. "too many args. my analysis of the signature says we ignore any arguments past " number-of-params " arguments but you've provided " number-of-args) + :severity message.severity.WARN})))))}) + (local lint-mt {:__tojson (fn [{: self} state] (dkjson.encode self state)) :__index #(. $1 :self $2)}) diff --git a/test/lint.fnl b/test/lint.fnl index 3421cb8..4310a81 100644 --- a/test/lint.fnl +++ b/test/lint.fnl @@ -201,18 +201,18 @@ (assert-ok "(do (print :x) 11)") ;; unnecessary do (check "(do 9)" - [{:message "unnecessary do" + [{:message "unnecessary unary do" :code :unnecessary-unary :range {:start {:character 0 :line 0} :end {:character 6 :line 0}}}]) ;; unnecessary values (check "(print :hey (values :lol))" [{:code :unnecessary-unary - :message "unnecessary values" + :message "unnecessary unary values" :range {:start {:character 12 :line 0} :end {:character 25 :line 0}}}]) (check "(+ (* 3) (* 4 4))" - [{:message "unnecessary *" + [{:message "unnecessary unary *" :code :unnecessary-unary :range {:start {:character 3 :line 0} :end {:character 8 :line 0}}}])