Add diagnostic for var-never-set.

This uses the isvar field from the opts map of the destructure hook.
This commit is contained in:
Phil Hagelberg 2023-12-05 21:09:04 -08:00 committed by XeroOl
parent f25fd9fd60
commit 5055fd31ef
4 changed files with 38 additions and 11 deletions

View File

@ -83,7 +83,7 @@ later by fennel-ls.language to answer requests from the client."
(when (or ?reference? (fennel.multi-sym? ast)) (when (or ?reference? (fennel.multi-sym? ast))
(reference ast scope))) (reference ast scope)))
(λ define [?definition binding scope] (λ define [?definition binding scope ?opts]
;; Add a definition to the definitions ;; Add a definition to the definitions
;; recursively explore the binding (which, in the general case, is a destructuring assignment) ;; recursively explore the binding (which, in the general case, is a destructuring assignment)
;; right now I'm not keeping track of *how* the symbol was destructured: just finding all the symbols for now. ;; right now I'm not keeping track of *how* the symbol was destructured: just finding all the symbols for now.
@ -96,7 +96,8 @@ later by fennel-ls.language to answer requests from the client."
:referenced-by (or (?. definitions binding :referenced-by) []) :referenced-by (or (?. definitions binding :referenced-by) [])
:keys (if (< 0 (length keys)) :keys (if (< 0 (length keys))
(fcollect [i 1 (length keys)] (fcollect [i 1 (length keys)]
(. keys i)))}] (. keys i)))
:var? (?. ?opts :isvar)}]
(tset (. definitions-by-scope scope) (tostring binding) definition) (tset (. definitions-by-scope scope) (tostring binding) definition)
(tset definitions binding definition)) (tset definitions binding definition))
(list? binding) (list? binding)
@ -104,7 +105,7 @@ later by fennel-ls.language to answer requests from the client."
(= (length binding) (= (length binding)
(- (length ?definition) 1))) (- (length ?definition) 1)))
(for [i 1 (length binding)] (for [i 1 (length binding)]
(define (. ?definition (+ i 1)) (. binding i) scope)) (define (. ?definition (+ i 1)) (. binding i) scope ?opts))
(recurse (. binding 1) keys)) (recurse (. binding 1) keys))
(table? binding) (table? binding)
(accumulate [prev nil (accumulate [prev nil
@ -135,10 +136,10 @@ later by fennel-ls.language to answer requests from the client."
;; (fcollect [i 1 (length keys)] ;; (fcollect [i 1 (length keys)]
;; (. keys i)))} ;; (. keys i)))}
name (string.match (tostring binding) "[^%.:]+")] name (string.match (tostring binding) "[^%.:]+")]
(when (multisym? binding) (case (find-definition (tostring name) scope)
(case (find-definition (tostring name) scope) target (if (multisym? binding)
target (table.insert target.referenced-by binding)
(table.insert target.referenced-by binding)))) (set target.var-set true))))
(= :table (type binding)) (= :table (type binding))
(each [k v (iter binding)] (each [k v (iter binding)]
(table.insert keys k) (table.insert keys k)
@ -146,11 +147,11 @@ later by fennel-ls.language to answer requests from the client."
(table.remove keys)))) (table.remove keys))))
(recurse binding [])) (recurse binding []))
(λ destructure [to from scope {:declaration ?declaration?}] (λ destructure [to from scope {:declaration ?declaration? &as opts}]
;; I really don't understand symtype ;; I really don't understand symtype
;; I think I need an explanation ;; I think I need an explanation
(if ?declaration? (if ?declaration?
(define to from scope) (define to from scope opts)
(mutate to from scope))) (mutate to from scope)))
(λ add-field [ast multisym scope] (λ add-field [ast multisym scope]

View File

@ -73,6 +73,15 @@ Goes through a file and mutates the `file.diagnostics` field, filling it with di
:code 304 :code 304
:codeDescription "bad-unpack"})))) :codeDescription "bad-unpack"}))))
(λ var-never-set [self file]
(icollect [symbol definition (pairs file.definitions) &into file.diagnostics]
(if (and definition.var? (not definition.var-set))
{:range (message.ast->range self file symbol)
:message (.. "var is never set: " (tostring symbol))
:severity message.severity.WARN
:code 301
:codeDescription "var-never-set"})))
(λ check [self file] (λ check [self file]
"fill up the file.diagnostics table with linting things" "fill up the file.diagnostics table with linting things"
(if self.configuration.checks.unused-definition (if self.configuration.checks.unused-definition
@ -82,6 +91,8 @@ Goes through a file and mutates the `file.diagnostics` field, filling it with di
(if self.configuration.checks.unnecessary-method (if self.configuration.checks.unnecessary-method
(unnecessary-method self file)) (unnecessary-method self file))
(if self.configuration.checks.bad-unpack (if self.configuration.checks.bad-unpack
(bad-unpack self file))) (bad-unpack self file))
(if self.configuration.checks.var-never-set
(var-never-set self file)))
{: check} {: check}

View File

@ -96,7 +96,8 @@ in the \"self\" object."
:checks {:unused-definition (option true) :checks {:unused-definition (option true)
:unknown-module-field (option true) :unknown-module-field (option true)
:unnecessary-method (option true) :unnecessary-method (option true)
:bad-unpack (option true)} :bad-unpack (option true)
:var-never-set (option true)}
:extra-globals (option "")}) :extra-globals (option "")})
(λ make-configuration [?c] (λ make-configuration [?c]

View File

@ -81,6 +81,20 @@
"not found") "not found")
_ (error "did not match")))) _ (error "did not match"))))
(it "warns about vars that are never set"
(let [self (create-client)
responses (self:open-file! filename "(var x nil) (print x)")]
(match responses
[{:params {: diagnostics}}]
(is (find [_ v (ipairs diagnostics)]
(match v
{:message "var is never set: x"
:range {:start {:character 5 :line 0}
:end {:character 6 :line 0}}}
v))
"not found")
_ (error "did not match"))))
(it "warns about unused functions" (it "warns about unused functions"
(let [self (create-client) (let [self (create-client)
responses (self:open-file! filename "(fn x [])")] responses (self:open-file! filename "(fn x [])")]