(import-macros {: is-matching : is-casing : describe : it : before-each} :test) (local is (require :test.is)) (local {: view} (require :fennel)) (local {: ROOT-URI : create-client} (require :test.client)) (local filename (.. ROOT-URI "/imaginary-file.fnl")) (fn check-completion [body line col expected unexpected ?line-start ?col-start] (let [client (doto (create-client) (: :open-file! filename body)) [{: result}] (client:completion filename line col) seen (if result (collect [_ suggestion (ipairs result)] (do (if ?line-start (is.same suggestion.textEdit.range.start {:line ?line-start :character ?col-start})) (values suggestion.label suggestion.label))))] (each [_ exp (ipairs expected)] (is (. seen exp) (.. exp " was not suggested, but should be"))) (each [_ exp (ipairs unexpected)] (is.nil (. seen exp) (.. exp " was suggested, but shouldn't be"))))) (describe "completions" (it "suggests globals" (check-completion "(" 0 1 [:_G :debug :table :io :getmetatable :setmetatable :_VERSION :ipairs :pairs :next] [] 0 1) (check-completion "#nil\n(" 1 1 [:_G :debug :table :io :getmetatable :setmetatable :_VERSION :ipairs :pairs :next] [] 1 1)) (it "suggests locals in scope" (check-completion "(local x 10)\n(print )" 1 7 [:x] [] 1 7)) (it "suggests locals where the definition can't be found" (check-completion "(local x (doto 10 or and +))\n(print )" 1 7 [:x] [] 1 7)) (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 2 [:do :doto] [] 0 1) ;; in fact, for fuzzy-matching clients, you especially want to make sure the server isn't filtering (check-completion "(t)" 0 2 [:doto :setmetatable] [] 0 1)) (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 " 2 4 [: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 with no body in the `let` and no close parentheses" (check-completion "(local foo 10)\n(local x (let [y f]\n" 1 18 [:foo] [])) (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] [:local])) (it "doesn't crash with a partially typed multisym contains ::" (check-completion "(local x {:field (fn [])})\n(x::f" 1 3 [] []))) ;; 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] [] 1 2)) (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] [] 1 14)) ;; ;; 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" (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" (check-completion "(local x {:field (fn [])})\n(x:fi" 1 5 [:field] [:table])) (describe "metadata" ;; CompletionItemKind (local kinds {:Text 1 :Method 2 :Function 3 :Constructor 4 :Field 5 :Variable 6 :Class 7 :Interface 8 :Module 9 :Property 10 :Unit 11 :Value 12 :Enum 13 :Keyword 14 :Snippet 15 :Color 16 :File 17 :Reference 18 :Folder 19 :EnumMember 20 :Constant 21 :Struct 22 :Event 23 :Operator 24 :TypeParameter 25}) (it "offers rich information about function completions" (let [client (doto (create-client) (: :open-file! filename "(fn xyzzy [x y z] \"docstring\" nil)\n(xyzz")) [{:result [completion]}] (client:completion filename 1 5)] ;; TODO this seems a little bit weird to assert (is.same :xyzzy completion.label "the first completion should be xyzzy") (assert completion.kind "completion kind should be present") (assert completion.documentation "completion documentation should be present"))) (it "offers rich information about builtin/special completions" (let [client (doto (create-client) (: :open-file! filename "(")) [{:result completions}] (client:completion filename 0 1) completion (accumulate [item nil _ completion (ipairs completions) &until item] (if (= completion.label :local) completion))] (is-casing completion (where {:label :local :kind (= kinds.Operator) :documentation documentation :textEdit {:range {:start {:line 0 :character 1} :end {:line 0 :character 1}}}} (not= documentation :nil))))) (it "offers rich information about builtin-macro completions" (let [client (doto (create-client) (: :open-file! filename "(")) [{:result completions}] (client:completion filename 0 1) completion (accumulate [item nil _ completion (ipairs completions) &until item] (if (= completion.label :-?>) completion))] (is-casing completion (where {:label :-?> :kind (= kinds.Keyword) :documentation documentation} (not= documentation :nil))))) (it "offers rich information about all builtin/globals" (let [client (doto (create-client) (: :open-file! filename "(")) [{:result completions}] (client:completion filename 0 1) _ (table.sort completions #(< $1.label $2.label)) missing-docs (icollect [_ completion (ipairs completions)] (if (not (and (= (type completion.label) :string) (= (type completion.kind) :number) (= (type completion.documentation) :table))) completion.label)) allowed-missing-docs {:lua true :set-forcibly! true ;; TODO support other lua versions besides 5.4 :gcinfo true :getfenv true :setfenv true :loadstring true :module true :newproxy true :unpack true :bit32 true ;; luajit :bit true :jit true}] (each [_ completion (ipairs completions)] (when (not (. allowed-missing-docs completion.label)) (is.same (type completion.label) :string "unlabeled completion") (is.same (type completion.kind) :number (.. completion.label " needs a kind")) (is.same (type completion.documentation) :table (.. completion.label " needs documentation")) (is.not.same completion.documentation :nil (.. completion.label " needs documentation")))))) (it "offers rich information about fields" (let [client (doto (create-client) (: :open-file! filename "(let [x (fn x [a b c] \"\"\"docstring\"\"\" nil)\n t {: x}]\n (t.")) [{:result completions}] (client:completion filename 2 5) _ (table.sort completions #(< $1.label $2.label)) missing-docs (icollect [_ completion (ipairs completions)] (if (not (and (= (type completion.label) :string) (= (type completion.kind) :number) (= (type completion.documentation) :table))) completion.label)) allowed-missing-docs {}] (each [_ completion (ipairs completions)] (when (not (. allowed-missing-docs completion.label)) (is.same (type completion.label) :string "unlabeled completion") (is.same (type completion.kind) :number (.. completion.label " needs a kind")) (is.same (type completion.documentation) :table (.. completion.label " needs documentation")) (is.not.same completion.documentation :nil (.. completion.label " needs documentation")))))))) ;; (it "offers rich information about variable completions") ;; (it "offers rich information about field completions") ;; (it "offers rich information about method completions") ;; (it "offers rich information about module completions") ;; (it "offers rich information about macro-module completions"))) ;; (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")