diff --git a/src/fennel-ls/lint.fnl b/src/fennel-ls/lint.fnl index bfeeb97..69804f1 100644 --- a/src/fennel-ls/lint.fnl +++ b/src/fennel-ls/lint.fnl @@ -16,7 +16,8 @@ the `file.diagnostics` field, filling it with diagnostics." :reference [] :macro-call [] :function-call [] - :special-call []}) + :special-call [] + :other []}) (local all-lints []) @@ -331,6 +332,25 @@ the `file.diagnostics` field, filling it with diagnostics." :message (.. "too many args. my analysis of the signature says we ignore any arguments past " min-params " arguments but you've provided " number-of-args) :severity message.severity.WARN})))))}) +(add-lint :duplicate-table-keys + {:type :other + :impl (fn [server file] + (let [seen []] + (each [ast (pairs file.lexical)] + (when (table? ast) + (case (getmetatable ast) + {: keys} (let [dkey (accumulate [_ 1 i v (ipairs keys) &until (. seen v)] + (do (set (. seen v) i) + (+ i 1)))] + (when (. keys dkey) + (coroutine.yield + {:code :duplicate-table-keys + :range (message.ast->range server file ast) + :message (.. "key " (tostring (. keys dkey)) " appears more than once") + :severity message.severity.WARN})) + (each [k (pairs seen)] + (set (. seen k) nil))))))))}) + (local lint-mt {:__tojson (fn [{: self} state] (dkjson.encode self state)) :__index #(. $1 :self $2)}) @@ -349,7 +369,9 @@ the `file.diagnostics` field, filling it with diagnostics." (table.insert file.diagnostics (wrap (doto diagnostic (tset :code lint.name)))))))) - + (each [diagnostic (coroutine.wrap #(run lints.other server file))] + (table.insert file.diagnostics + (wrap diagnostic))) (each [symbol definition (pairs file.definitions)] (when (. file.lexical symbol) (run lints.definition server file symbol definition))) diff --git a/test/lint.fnl b/test/lint.fnl index 93ea25e..a389b07 100644 --- a/test/lint.fnl +++ b/test/lint.fnl @@ -315,6 +315,21 @@ :flsproject.fnl "{:lints {:mismatched-argument-count true}}"}) nil) +(fn test-duplicate-keys [] + (assert-ok "{:a 1 :b 2}") + (assert-ok "(local _ {:a 1}) {:a 2}") + (check "{:a 1 :a 2}" [{:code :duplicate-table-keys :message "key a appears more than once"}]) + (check "{:there :are + :lots :of + :choices :for + :which :key + :to :include + :in :the + :message :. + :which :one?}" [{:code :duplicate-table-keys :message "key which appears more than once"}]) + (check "(local a 1) {:a 2 : a}" [{:code :duplicate-table-keys}]) + nil) + ;; TODO lints: ;; duplicate keys in kv table ;; (tset ) --> (set (. )) (might be wanted for compat?) @@ -341,4 +356,5 @@ : test-op-with-no-arguments : test-empty-let : test-decreasing-comparison - : test-arg-count} + : test-arg-count + : test-duplicate-keys}