fennel-ls/test/completion-test.fnl
2023-03-27 20:56:03 -05:00

127 lines
5.5 KiB
Fennel

(import-macros {: is-matching : describe : it : before-each} :test)
(local is (require :test.is))
(local {: view} (require :fennel))
(local {: ROOT-URI
: create-client} (require :test.mock-client))
(local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message))
(local filename (.. ROOT-URI "/imaginary-file.fnl"))
(fn check-completion [body line col expected ?unexpected]
(let [client (doto (create-client)
(: :open-file! filename body))
response (client:completion filename line col)
seen (collect [_ suggestion (ipairs (. response 1 :result))]
suggestion.label suggestion.label)]
(if expected
(each [_ exp (ipairs expected)]
(is (. seen exp) (.. exp " was not suggested, but should be"))))
(if ?unexpected
(each [_ exp (ipairs ?unexpected)]
(is.nil (. seen exp) (.. exp " was suggested, but shouldn't be"))))))
(fn check-no-completion [body line col expected ?unexpected]
(let [client (doto (create-client)
(: :open-file! filename body))
response (client:completion filename line col)]
(is-matching (. response 1)
{:jsonrpc "2.0" :id id :result nil}
"there shouldn't be a result")))
(describe "completions"
(it "suggests globals"
(check-completion "(" 0 1 [:_G :debug :table :io :getmetatable :setmetatable :_VERSION :ipairs :pairs :next]))
(it "suggests locals in scope"
(check-completion "(local x 10)\n(print )" 1 7 [:x]))
(it "suggests locals in scope at the top level"
(check-completion "(local x 10)\n\n" 1 0 [:x]))
(it "suggests more locals in scope"
(check-completion "(let [x 10] (let [y 100] \n nil\n ))" 2 4 [:x :y]))
(it "suggests specials and macros at beginning of list"
(check-completion "()" 0 1 [:do :let :fn] :doto :-> :-?>> :?.)
;; it's not the language server's job to do filtering,
;; so there's no negative assertions here for other symbols
(check-completion "(d)" 0 3 [:do :doto]))
(it "suggests macros in scope"
(check-completion "(macro funny [] `nil)\n()" 1 1 [:funny]))
(it "does not suggest locals out of scope"
(check-completion "(do (local x 10))\n" 1 0 [] [:x]))
(it "does not suggest function args out of scope"
(check-completion "(fn [x] (print x))\n" 1 0 [] [:x])
(check-completion "(fn [x] (print x))\n(print " 1 7 [] [:x]))
(describe "When the program doesn't compile"
(it "still completes without requiring the close parentheses"
(check-completion "(fn foo [z]\n (let [x 10 y 20]\n " 1 2 [:x :y :z]))
(it "still completes with no body in the `let`"
(check-completion "(let [x 10 y 20]\n )" 1 2 [:x :y]))
(it "still completes items from the previous definitions in the same `let`"
(check-completion "(let [a 10\n b 20\n " 1 6 [:a :b]))
;; (it "completes fields with a partially typed multisym that ends in :"
;; (check-completion "(local x {:field (fn [])})\n(x:" 1 3 [:field]))
(it "doesn't crash with a partially typed multisym contains ::"
(check-no-completion "(local x {:field (fn [])})\n(x::f" 1 3 [:field])))
;; Functions
(it "suggests function arguments at the top scope of the function"
(check-completion "(fn foo [arg1 arg2 arg3]\n )" 1 2 [:arg1 :arg2 :arg3]))
(it "suggests function arguments at the top scope of the function"
(check-completion "(fn foo [arg1 arg2 arg3]\n (do (do (do ))))" 1 14 [:arg1 :arg2 :arg3]))
;; ;; Scope Ordering Rules
;; (it "does not suggest locals past the suggestion location when a symbol is partially typed")
;; (it "does not suggest locals past the suggestion location without a symbol")
;; (it "does not suggest locals past the suggestion point at the top level")
;; (it "does not suggest items from later definitions in the same `let`")
;; (it "does not suggest macros defined from later definitions")
;; ;; Call ordering rules
;; (it "doesn't suggest specials in the middle of a list (open paren required)"
;; (check-completion "(do )"
;; 0 4 [] [:do :let :fn :-> :-?>> :?.])))
;; (it "doesn't suggest specials at the very top level")
;; (it "doesn't suggest macros in the middle of a list (open paren required)")
;; (it "doesn't suggest macros at the very top level")
(it "suggests fields of tables"
(check-completion
"(let [my-table {:foo 10 :bar 20}]\n my-table.)))"
1 11
[:foo :bar]
[:_G :local :doto :1])) ;; no globals, specials, macros, or others
(it "suggests fields of tables indirectly"
(check-completion
"(let [foo (require :foo)]\n foo.)))"
1 6
[:my-export :constant]
[:_G :local :doto :1]))) ;; no globals, specials, macros, or others
;; (it "suggests fields of strings"))
;; (it "suggests known fn fields of tables when using a method call multisym")
;; (it "suggests known fn keys when using the `:` special")
;; (it "suggests known keys when using the `.` special")
;; (it "suggests known module names in `require` and `include` and `import-macros` and `require-macros` and friends")
;; (it "knows the fields of the standard lua library.")
;; (it "suggests special forms for the call position of a list, but not other positions")
;; (it "does not suggest special forms for the \"call\" position when a list isn't actually a call, ie destructuring assignment")
;; (it "suggests keys when typing out destructuring, as in `(local {: typinghere} (require :mod))`")
;; (it "only suggests tables for `ipairs` / begin work on type checking system")