From 5055fd31ef21297c063b099235272ec7698e2983 Mon Sep 17 00:00:00 2001 From: Phil Hagelberg Date: Tue, 5 Dec 2023 21:09:04 -0800 Subject: [PATCH] Add diagnostic for var-never-set. This uses the isvar field from the opts map of the destructure hook. --- src/fennel-ls/compiler.fnl | 19 ++++++++++--------- src/fennel-ls/diagnostics.fnl | 13 ++++++++++++- src/fennel-ls/state.fnl | 3 ++- test/diagnostic-test.fnl | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl index 68aba71..5feb4c3 100644 --- a/src/fennel-ls/compiler.fnl +++ b/src/fennel-ls/compiler.fnl @@ -83,7 +83,7 @@ later by fennel-ls.language to answer requests from the client." (when (or ?reference? (fennel.multi-sym? ast)) (reference ast scope))) - (λ define [?definition binding scope] + (λ define [?definition binding scope ?opts] ;; Add a definition to the definitions ;; 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. @@ -96,7 +96,8 @@ later by fennel-ls.language to answer requests from the client." :referenced-by (or (?. definitions binding :referenced-by) []) :keys (if (< 0 (length keys)) (fcollect [i 1 (length keys)] - (. keys i)))}] + (. keys i))) + :var? (?. ?opts :isvar)}] (tset (. definitions-by-scope scope) (tostring binding) definition) (tset definitions binding definition)) (list? binding) @@ -104,7 +105,7 @@ later by fennel-ls.language to answer requests from the client." (= (length binding) (- (length ?definition) 1))) (for [i 1 (length binding)] - (define (. ?definition (+ i 1)) (. binding i) scope)) + (define (. ?definition (+ i 1)) (. binding i) scope ?opts)) (recurse (. binding 1) keys)) (table? binding) (accumulate [prev nil @@ -135,10 +136,10 @@ later by fennel-ls.language to answer requests from the client." ;; (fcollect [i 1 (length keys)] ;; (. keys i)))} name (string.match (tostring binding) "[^%.:]+")] - (when (multisym? binding) - (case (find-definition (tostring name) scope) - target - (table.insert target.referenced-by binding)))) + (case (find-definition (tostring name) scope) + target (if (multisym? binding) + (table.insert target.referenced-by binding) + (set target.var-set true)))) (= :table (type binding)) (each [k v (iter binding)] (table.insert keys k) @@ -146,11 +147,11 @@ later by fennel-ls.language to answer requests from the client." (table.remove keys)))) (recurse binding [])) - (λ destructure [to from scope {:declaration ?declaration?}] + (λ destructure [to from scope {:declaration ?declaration? &as opts}] ;; I really don't understand symtype ;; I think I need an explanation (if ?declaration? - (define to from scope) + (define to from scope opts) (mutate to from scope))) (λ add-field [ast multisym scope] diff --git a/src/fennel-ls/diagnostics.fnl b/src/fennel-ls/diagnostics.fnl index 40d9181..349ca56 100644 --- a/src/fennel-ls/diagnostics.fnl +++ b/src/fennel-ls/diagnostics.fnl @@ -73,6 +73,15 @@ Goes through a file and mutates the `file.diagnostics` field, filling it with di :code 304 :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] "fill up the file.diagnostics table with linting things" (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 (unnecessary-method self file)) (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} diff --git a/src/fennel-ls/state.fnl b/src/fennel-ls/state.fnl index b2902d0..1ba9b4d 100644 --- a/src/fennel-ls/state.fnl +++ b/src/fennel-ls/state.fnl @@ -96,7 +96,8 @@ in the \"self\" object." :checks {:unused-definition (option true) :unknown-module-field (option true) :unnecessary-method (option true) - :bad-unpack (option true)} + :bad-unpack (option true) + :var-never-set (option true)} :extra-globals (option "")}) (λ make-configuration [?c] diff --git a/test/diagnostic-test.fnl b/test/diagnostic-test.fnl index 4e83f68..5bd84fb 100644 --- a/test/diagnostic-test.fnl +++ b/test/diagnostic-test.fnl @@ -81,6 +81,20 @@ "not found") _ (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" (let [self (create-client) responses (self:open-file! filename "(fn x [])")]