better messages for not-enough-arguments and too-many-arguments

This commit is contained in:
XeroOl 2025-07-18 13:23:10 -05:00
parent 284a760224
commit 319a2ddf10
2 changed files with 48 additions and 34 deletions

View File

@ -623,24 +623,11 @@ You can read more about how to add lints in docs/linting.md"
{:indeterminate nil &as result}
(case (?. (navigate.getmetadata server result) :fnl/arglist)
signature
(let [number-of-args (- (length ast)
(if (and (sym? (. ast 1))
(string.find (tostring (. ast 1)) ".:"))
0 ; method call; the head counts as an argument
1)) ; function call; the head doesn't count as an argument
(let [number-of-args (- (length ast) 1)
passes-extra-args (and (not= 1 (length ast))
(not (special? (. ast 1)))
(not (. file.macro-calls ast))
(possibly-multival? (. ast (length ast))))
min-params (accumulate [last-required-argument nil
i arg (ipairs signature)
&until (let [s (tostring arg)]
(or (= s "...")
(= s "&")))]
(let [first-char (string.sub (tostring arg) 1 1)]
(if (and (not= first-char "?") (not= first-char "_"))
i
last-required-argument)))
;; exception: (- a b) only needs 1 argument
;; exception: (/ a b) only needs 1 argument
min-params (if (= result (docs.get-builtin server :-)) 1
@ -649,11 +636,23 @@ You can read more about how to add lints in docs/linting.md"
;; exception: fn only needs one argument
(= result (docs.get-builtin server :fn)) 1
(= result (docs.get-builtin server :collect)) 2
(or min-params 0))]
(or (accumulate [last-required-param nil
i arg (ipairs signature)
&until (let [s (tostring arg)]
(or (= s "...")
(= s "&")))]
(let [first-char (string.sub (tostring arg) 1 1)]
(if (and (not= first-char "?") (not= first-char "_"))
i
last-required-param)))
0))
method-call? (and (sym? (. ast 1))
(string.find (tostring (. ast 1)) ".:"))
min-params (- min-params (if method-call? 1 0))]
(if (and (< number-of-args min-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 " min-params " arguments but I only see " number-of-args)
:message (.. (view (. ast 1)) " expects at least " min-params " argument(s); found " number-of-args)
:severity message.severity.WARN})))))})
(add-lint :too-many-arguments
@ -690,25 +689,33 @@ You can read more about how to add lints in docs/linting.md"
{:indeterminate nil &as result}
(case (?. (navigate.getmetadata server result) :fnl/arglist)
signature
(let [number-of-args (- (length ast)
(if (and (sym? (. ast 1))
(string.find (tostring (. ast 1)) ".:"))
0 ; method call; the head counts as an argument
1)) ; function call; the head doesn't count as an argument
(infinite-params?) (accumulate [vararg nil
_ arg (ipairs signature)
&until vararg]
(let [s (tostring arg)]
(or (= s "...") (= s "&"))))
(let [number-of-args (- (length ast) 1)
infinite-params? (accumulate [vararg nil
_ arg (ipairs signature)
&until vararg]
(let [s (tostring arg)]
(or (= s "...") (= s "&"))))
;; exception: (table.insert table item) can take a third argument
max-params (if (= result (. (docs.get-global server :table) :fields :insert))
3
(length signature))]
(length signature))
method-call? (and (sym? (. ast 1))
(string.find (tostring (. ast 1)) ".:"))
max-params (- max-params (if method-call? 1 0))]
(if (and (< max-params number-of-args)
(not infinite-params?))
{:range (message.ast->range server file ast)
:message (.. "too many args. my analysis of the signature says we ignore any arguments past " max-params " arguments but you've provided " number-of-args)
:severity message.severity.WARN})))))})
(let [range-of-call (message.ast->range server file ast)
first-bad-argument (. ast (+ 1 max-params))
?range-of-first-bad-argument (message.ast->range server file first-bad-argument)
range {:start (or (?. ?range-of-first-bad-argument :start) range-of-call.start)
:end range-of-call.end}]
{: range
:message (if (= max-params -1)
;; this is when you call a 0 argument function using a `:`
(.. (: (view (. ast 1)) :gsub ":" ".") " expects 0 arguments; found 1")
(.. (view (. ast 1)) " expects at most " max-params " argument(s); found " number-of-args))
:severity message.severity.WARN}))))))})
(add-lint :duplicate-table-keys
{:what-it-does

View File

@ -306,16 +306,23 @@
;; methods
(let [add-opts #{:main.fnl $ :flsproject.fnl "{:lints {:not-enough-arguments true}}"}]
(check (add-opts "(fn foo [a b c ?d ?e] (print a b c ?d ?e))\n(foo 1 2)")
[{:code :not-enough-arguments}])
[{:code :not-enough-arguments
:message "foo expects at least 3 argument(s); found 2"}])
(assert-ok (add-opts "(fn foo [a b c ?d ?e] (print a b c ?d ?e))\n(foo 1 2 3)"))
(assert-ok (add-opts "(fn foo [a b c ?d ?e] (print a b c ?d ?e))\n(foo 1 2 3 4 5)"))
(check (add-opts "(fn foo [a b c ?d ?e] (print a b c ?d ?e))\n(foo 1 2 3 4 5 6)")
[{:code :too-many-arguments}])
[{:code :too-many-arguments
:message "foo expects at most 5 argument(s); found 6"}])
(assert-ok (add-opts "(let [f :hi] (f:byte))"))
(check (add-opts "(let [f :hi] (f:sub))")
[{:code :not-enough-arguments}])
[{:code :not-enough-arguments
:message "f:sub expects at least 1 argument(s); found 0"}])
(check (add-opts "(let [f :hi] (f:sub 1 2 3))")
[{:code :too-many-arguments}])
[{:code :too-many-arguments
:message "f:sub expects at most 2 argument(s); found 3"}])
(check (add-opts "(let [obj {:field (fn foo [])}] (obj:field))")
[{:code :too-many-arguments
:message "obj.field expects 0 arguments; found 1"}])
(assert-ok (add-opts "(let [foo 10] (fn [] foo))"))
(assert-ok (add-opts "(fn [])"))
nil))