introduce mismatched-argument-count lint

This commit is contained in:
XeroOl 2025-07-11 19:16:06 -05:00
parent 772d59fff1
commit 5143310b90
5 changed files with 55 additions and 18 deletions

View File

@ -169,7 +169,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
(sym? head :λ) (sym? head :λ)
(sym? head :hashfn)))) (sym? head :hashfn))))
(search-multival server file (. definition (length definition)) stack multival opts) (search-multival server file (. definition (length definition)) stack multival opts)
result result result_ {:indeterminate true}
_ (case (docs.get-builtin server (tostring (. call 1))) _ (case (docs.get-builtin server (tostring (. call 1)))
{:metadata metadata_} {:indeterminate true}))))))) {:metadata metadata_} {:indeterminate true})))))))
@ -281,19 +281,19 @@ initialization-opts: {:stack ?list[ast]
_ child (ipairs ast) _ child (ipairs ast)
&until result] &until result]
(if (contains? child byte) (if (contains? child byte)
(recurse child byte))) (recurse child)))
(and (not (sym? ast)) (not (varg? ast))) (and (not (sym? ast)) (not (varg? ast)))
(accumulate [(result _parent) nil (accumulate [(result _parent) nil
key value (pairs ast) key value (pairs ast)
&until result] &until result]
(if (contains? key byte) (if (contains? key byte)
(recurse key byte) (recurse key)
(contains? value byte) (contains? value byte)
(recurse value byte))))))) (recurse value)))))))
(values (values
(accumulate [result nil _ top-level-form (ipairs ast) &until result] (accumulate [result nil _ top-level-form (ipairs ast) &until result]
(if (contains? top-level-form byte) (if (contains? top-level-form byte)
(recurse top-level-form byte))) (recurse top-level-form)))
(fcollect [i 1 (length parents)] (fcollect [i 1 (length parents)]
(. parents (- (length parents) i -1))))) (. parents (- (length parents) i -1)))))

View File

@ -38,7 +38,7 @@ in the \"server\" object."
(or (get-by-uri server uri) (or (get-by-uri server uri)
;; if the cached uri isn't found, clear the cache and try again ;; if the cached uri isn't found, clear the cache and try again
(do (tset modules module nil) (do (tset modules module nil)
(get-by-module server module))) (get-by-module server module macro?)))
nil nil
(case (searcher.lookup server module macro?) (case (searcher.lookup server module macro?)
uri uri

View File

@ -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 "" header ;; base case. empty line marks end of header
line (let [(k v) (line:match "^(.-): (.-)$")] line (let [(k v) (line:match "^(.-): (.-)$")]
(if (not (and k v)) (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) (tset header k v)
(read-header in header)))))) (read-header in header))))))

View File

@ -8,6 +8,7 @@ the `file.diagnostics` field, filling it with diagnostics."
(local analyzer (require :fennel-ls.analyzer)) (local analyzer (require :fennel-ls.analyzer))
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))
(local utils (require :fennel-ls.utils)) (local utils (require :fennel-ls.utils))
(local navigate (require :fennel-ls.navigate))
(local dkjson (require :dkjson)) (local dkjson (require :dkjson))
(local lints {:definition [] (local lints {:definition []
@ -125,7 +126,7 @@ the `file.diagnostics` field, filling it with diagnostics."
(. redundant-wrappers (tostring (. ast 1))) (. redundant-wrappers (tostring (. ast 1)))
(= (length ast) 2)) (= (length ast) 2))
{:range (message.ast->range server file ast) {:range (message.ast->range server file ast)
:message (.. "unnecessary " (tostring (. ast 1))) :message (.. "unnecessary unary " (tostring (. ast 1)))
:severity message.severity.WARN :severity message.severity.WARN
:fix #{:title "Unwrap the expression" :fix #{:title "Unwrap the expression"
:changes [{:range (message.ast->range server file ast) :changes [{:range (message.ast->range server file ast)
@ -247,14 +248,14 @@ the `file.diagnostics` field, filling it with diagnostics."
(add-lint :inline-unpack (add-lint :inline-unpack
{:type [:function-call :special-call] {: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 "generally, values and unpack are signs that the user is trying to do
something with multiple values. However, multiple values will get something with multiple values. However, multiple values will get
\"adjusted\" to one value if they don't come at the end of the call." \"adjusted\" to one value if they don't come at the end of the call."
(faccumulate [f nil index 2 (length call) &until f] (faccumulate [f nil index 2 (length ast) &until f]
(let [arg (. call index)] (let [arg (. ast index)]
(if (and (or (op? (. call 1)) (not (special? (. call 1)))) (if (and (or (op? (. ast 1)) (not (special? (. ast 1))))
(not= index (length call)) (not= index (length ast))
(list? arg) (list? arg)
(or (sym? (. arg 1) :values) (or (sym? (. arg 1) :values)
(sym? (. arg 1) :unpack) (sym? (. arg 1) :unpack)
@ -267,8 +268,8 @@ the `file.diagnostics` field, filling it with diagnostics."
(add-lint :empty-let (add-lint :empty-let
{:type :special-call {:type :special-call
:impl (fn [server file call] :impl (fn [server file ast]
(case call (case ast
(where [let* binding] (where [let* binding]
(sym? let* :let) (sym? let* :let)
(fennel.sequence? binding) (fennel.sequence? binding)
@ -282,6 +283,42 @@ the `file.diagnostics` field, filling it with diagnostics."
{:range {: start : end} {:range {: start : end}
:newText "do"})]}}))}) :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)) (local lint-mt {:__tojson (fn [{: self} state] (dkjson.encode self state))
:__index #(. $1 :self $2)}) :__index #(. $1 :self $2)})

View File

@ -201,18 +201,18 @@
(assert-ok "(do (print :x) 11)") (assert-ok "(do (print :x) 11)")
;; unnecessary do ;; unnecessary do
(check "(do 9)" (check "(do 9)"
[{:message "unnecessary do" [{:message "unnecessary unary do"
:code :unnecessary-unary :code :unnecessary-unary
:range {:start {:character 0 :line 0} :range {:start {:character 0 :line 0}
:end {:character 6 :line 0}}}]) :end {:character 6 :line 0}}}])
;; unnecessary values ;; unnecessary values
(check "(print :hey (values :lol))" (check "(print :hey (values :lol))"
[{:code :unnecessary-unary [{:code :unnecessary-unary
:message "unnecessary values" :message "unnecessary unary values"
:range {:start {:character 12 :line 0} :range {:start {:character 12 :line 0}
:end {:character 25 :line 0}}}]) :end {:character 25 :line 0}}}])
(check "(+ (* 3) (* 4 4))" (check "(+ (* 3) (* 4 4))"
[{:message "unnecessary *" [{:message "unnecessary unary *"
:code :unnecessary-unary :code :unnecessary-unary
:range {:start {:character 3 :line 0} :range {:start {:character 3 :line 0}
:end {:character 8 :line 0}}}]) :end {:character 8 :line 0}}}])