clean up some tests, restore (fn M.method []) completion handling

This commit is contained in:
XeroOl 2025-04-23 22:38:46 -05:00
parent 2800038005
commit 3f1c2ea361
4 changed files with 68 additions and 66 deletions

View File

@ -20,6 +20,7 @@
* Ignore unused locals if they end in underscore.
* Settings file: `flsproject.fnl`. Settings are now editor agnostic.
* Support `:intersection` as a Lua version; only includes globals present in every Lua.
* Support better completions. The eglot client is no longer a special case.
### Changes

View File

@ -83,14 +83,26 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
:keys ?keys
:multival ?multival
:fields ?fields}} assignment]
(when (and (= 0 (length stack))
opts.save-last-binding)
(tset opts.save-last-binding 1 assignment.target))
(if (and (= 0 (length stack)) opts.stop-early?)
assignment.target ;; BASE CASE!!
;; search a virtual field from :fields
;; :fields
(and (not= 0 (length stack)) (?. ?fields (. stack (length stack))))
(search-assignment server file {:target (. ?fields (table.remove stack))} stack opts)
;; This case is hard to explain.
;; Under these conditions, search-multival is planning on returning {:definition ?definition : file}
;; but assignment.target is more rich with information.
;; Specifically, it has the :fields key, which is useful in finding non-local fields.
;; For example, The `my-method` field of `M` in `(local M {})\n(fn M.my-method [])`
;; is shared using this :fields mechanism.
(and (not (list? ?definition))
(not (varg? ?definition))
(= 1 (or 1 ?multival))
(not (sym? ?definition))
(= 0 (length stack)))
assignment.target
(search-multival server file ?definition (stack-add-keys! stack ?keys) (or ?multival 1) opts))))
(λ search-reference [server file ref stack opts]
@ -212,10 +224,10 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
opts (or ?opts {})]
(case (docs.get-builtin server (. split 1))
metadata (search-document server metadata stack opts)
_ (case (docs.get-global server (. split 1))
metadata (search-document server metadata stack opts)
_ (case (find-local-definition file name scope)
def (search-val server file def.definition (stack-add-keys! stack def.keys) opts))))))
_ (case (find-local-definition file name scope)
def (search-val server file def.definition (stack-add-keys! stack def.keys) opts)
_ (case (docs.get-global server (. split 1))
metadata (search-document server metadata stack opts))))))
(λ past? [?ast byte]
;; check if a byte is past an ast object

View File

@ -29,7 +29,7 @@
file.scope)
range (if ?symbol (message.ast->range server file ?symbol) {:start position :end position})
results []
seen []]
seen {}]
(fn add-completion [definition name]
(table.insert results
@ -53,45 +53,54 @@
(and (fennel.list? def.definition)
;; TODO check that arg is called `self`
(or (and (fennel.sym? (. def.definition 1) "fn")
(fennel.sym? (. def.definition 2) "self"))
(fennel.sym? (?. def.definition 2 1) "self"))
(and (fennel.sym? (. def.definition 1) "λ")
(fennel.sym? (. def.definition 2) "self")))))
(fennel.sym? (?. def.definition 2 1) "self")))))
(add-completion-recursively def (.. name ":" field))
(add-completion-recursively def (.. name "." field)))
_ (do
(io.stderr:write "BAD!!!! undocumented field: " (tostring field) "\n")
{:label field})))))
(when definition.fields
(each [field value (pairs definition.fields)]
(when (= (type field) :string)
(if (or (= :self (tostring (?. value :metadata :fnl/arglist 1)))
(and (fennel.list? value.definition)
;; TODO check that arg is called `self`
(or (and (fennel.sym? (. value.definition 1) "fn")
(fennel.sym? (. value.definition 2) "self"))
(and (fennel.sym? (. value.definition 1) "λ")
(fennel.sym? (. value.definition 2) "self")))))
(add-completion-recursively value (.. name ":" field))
(add-completion-recursively value (.. name "." field))))))
(each [field value (pairs definition.fields)]
(when (= (type field) :string)
(if (or (= :self (tostring (?. value :metadata :fnl/arglist 1)))
(and (fennel.list? value.definition)
;; TODO check that arg is called `self`
(or (and (fennel.sym? (. value.definition 1) "fn")
(fennel.sym? (?. value.definition 2 1) "self"))
(and (fennel.sym? (. value.definition 1) "λ")
(fennel.sym? (?. value.definition 2 1) "self")))))
(add-completion-recursively value (.. name ":" field))
(add-completion-recursively value (.. name "." field))))))
(set (. seen definition) false)))
;; end yield
(local seen-manglings {})
(each [_ global* (ipairs file.allowed-globals)]
(case (analyzer.search-name-and-scope server file global* scope)
def (if (and (= :_G (tostring global*))
(not (: (tostring ?symbol) :match "_G[:.]")))
(add-completion def global*)
(add-completion-recursively def global*))
_ (do
(io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n")
{:label global*})))
(when (not (. seen-manglings global*))
(set (. seen-manglings global*) true)
(case (analyzer.search-name-and-scope server file global* scope)
def (if (and (= :_G (tostring global*))
(not (: (tostring ?symbol) :match "_G[:.]")))
(add-completion def global*)
(add-completion-recursively def global*))
_ (do
(io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n")
{:label global*}))))
(var scope scope)
(while scope
(each [mangling (pairs scope.manglings)]
(case (analyzer.search-name-and-scope server file mangling scope)
def (add-completion-recursively def mangling)
_ (add-completion-recursively {} mangling)))
(when (not (. seen-manglings mangling))
(set (. seen-manglings mangling) true)
(case (analyzer.search-name-and-scope server file mangling scope)
def (add-completion-recursively def mangling)
_ (add-completion-recursively {} mangling))))
(when in-call-position?
(each [macro* (pairs scope.macros)]
(table.insert results {:label macro* :filterText macro* :textEdit {:newText macro* : range}}))

View File

@ -154,29 +154,29 @@
nil)
(fn test-field []
(check "(local x {:field (fn [])})\n(x:" [:x:field] [])
(check "(local x {:field (fn [self])})\n(x:" [:x:field] [])
(check "(local x {:field (fn [self])})\n(x:fi|" [:x:field] [])
;; regression test for not crashing
(check "(local x {:field (fn [])})\n(x::f" [] [])
(check "(local x {:field (fn [self])})\n(x::f" [] [])
(check
"(let [my-table {:foo 10 :bar 20}]\n my-table.|)))"
[:foo :bar]
[:local :doto :+]) ;; no specials or macros because it's not in a head position
[:my-table.foo :my-table.bar]
[])
(check
{:main.fnl "(let [foo (require :fooo)]
foo.|)))"
:fooo.fnl "(fn my-export [x] (print x))
{: my-export :constant 10}"}
[:my-export :constant]
[:_G :local :doto :+])
[:foo.my-export :foo.constant]
[])
(check
{:main.fnl "(let [foo (require :fooo)]
foo.|)))"
:fooo.fnl "(local M {:constant 10})
(fn M.my-export [x] (print x))
M"}
[:my-export :constant]
[:_G :local :doto :+]) ;; no globals, specials, macros, or others
(check "(local x {:field (fn [])})\n(x:fi|" [:field] [:table])
[:foo.my-export :foo.constant]
[])
nil)
(fn test-docs []
@ -231,12 +231,11 @@
(check "(coroutine.y|"
[{:label "yield"
:documentation #(and $.value ($.value:find "```fnl\n(coroutine.yield ...)\n```" 1 true))}]
["coroutine" "_G" "do"
{:documentation #(= nil $)}])
[{:documentation #(= nil $)}])
(check "(local c coroutine)
(c.y"
[{:label "yield"}]
["coroutine" "_G" "do"])
["coroutine.yield" "c.yield"]
[{:documentation #(= nil $)}])
(check "(local t table)
(t.i"
["insert"]
@ -248,24 +247,6 @@
nil)
(fn test-eglot-fields []
"tests for handling Eglot specially"
(check "(coroutine.y|"
[{:label "coroutine.yield"
:filterText "coroutine.yield"
:documentation #(and $.value ($.value:find "```fnl\n(coroutine.yield ...)\n```" 1 true))}]
[])
(check "(local c coroutine)
(c.y"
[{:label "c.yield"
:filterText "c.yield"
:textEdit #(= nil $)}]
[])
(check "(local x {:field (fn [])})
(x:"
[:x:field]
[]))
;; ;; Future tests / features
;; ;; Scope Ordering Rules
;; (it "does not suggest locals past the suggestion location when a symbol is partially typed")
@ -292,5 +273,4 @@
: test-fn-arg
: test-field
: test-docs
: test-module
: test-eglot-fields}
: test-module}