Now it does a two-level-deep search when creating competions for tables, which means that a completion for module fields have better metadata. the completion code is a bit of a mess, so I want to look into refactoring it soon.
232 lines
11 KiB
Fennel
232 lines
11 KiB
Fennel
(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]
|
|
(let [client (doto (create-client)
|
|
(: :open-file! filename body))
|
|
[{: result}] (client:completion filename line col)
|
|
seen (if result
|
|
(collect [_ suggestion (ipairs 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"))))))
|
|
|
|
(describe "completions"
|
|
(it "suggests globals"
|
|
(check-completion "(" 0 1 [:_G :debug :table :io :getmetatable :setmetatable :_VERSION :ipairs :pairs :next])
|
|
(check-completion "#nil\n(" 1 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 where the definition can't be found"
|
|
(check-completion "(local x (doto 10 or and +))\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 2 [: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 " 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]))
|
|
|
|
(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"
|
|
(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}
|
|
(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
|
|
|
|
;; TODO remove these from fennel-ls when they become configurable
|
|
:love true
|
|
:vim 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")
|