New "unknown module field" diagnostic, and the groundwork for global completions with documentation
This commit is contained in:
parent
20b9094592
commit
8001c91120
@ -234,7 +234,7 @@ later by fennel-ls.language to answer requests from the client."
|
||||
(error "__NOT_AN_ERROR")))
|
||||
|
||||
(local allowed-globals
|
||||
(icollect [k v (pairs _G)]
|
||||
(icollect [k _ (pairs _G)]
|
||||
k))
|
||||
(table.insert allowed-globals :vim)
|
||||
|
||||
@ -273,10 +273,6 @@ later by fennel-ls.language to answer requests from the client."
|
||||
; {:range (message.pos->range 0 0 0 0)
|
||||
; :message (.. "unrecoverable compiler error: " err)})
|
||||
|
||||
;; analyze more things
|
||||
;; write things back to the file object
|
||||
(local deep-references {})
|
||||
|
||||
; (each [sym target (pairs references)]
|
||||
; (if
|
||||
; (sym? target)
|
||||
@ -284,18 +280,6 @@ later by fennel-ls.language to answer requests from the client."
|
||||
; (= :table (type target))))
|
||||
; ;; base case???
|
||||
|
||||
(each [sym definition (pairs definitions)]
|
||||
(if (and self.configuration.checks.unused-definition
|
||||
(= 0 (length definition.referenced-by))
|
||||
(not= "_" (: (tostring sym) :sub 1 1)))
|
||||
(let [range (message.ast->range sym file)]
|
||||
(table.insert diagnostics
|
||||
{:range range
|
||||
:message (.. "unused definition: " (tostring sym))
|
||||
:severity message.severity.WARN
|
||||
:code 301
|
||||
:codeDescription "warning error"}))))
|
||||
|
||||
(set file.ast ast)
|
||||
(set file.scope scope)
|
||||
(set file.scopes scopes)
|
||||
|
||||
36
src/fennel-ls/diagnostics.fnl
Normal file
36
src/fennel-ls/diagnostics.fnl
Normal file
@ -0,0 +1,36 @@
|
||||
(local language (require :fennel-ls.language))
|
||||
(local message (require :fennel-ls.message))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
|
||||
(λ unused-definition [self file]
|
||||
"local variable that is defined but not used"
|
||||
(icollect [symbol definition (pairs file.definitions) &into file.diagnostics]
|
||||
(if (and (= 0 (length definition.referenced-by))
|
||||
(not= "_" (: (tostring symbol) :sub 1 1)))
|
||||
{:range (message.ast->range symbol file)
|
||||
:message (.. "unused definition: " (tostring symbol))
|
||||
:severity message.severity.WARN
|
||||
:code 301
|
||||
:codeDescription "warning error"})))
|
||||
|
||||
(λ unknown-module-field [self file]
|
||||
"any multisym whose definition can't be found through a (require) call"
|
||||
(icollect [symbol (pairs file.references) &into file.diagnostics]
|
||||
(if (. (utils.multi-sym-split symbol) 2)
|
||||
(let [opts {}
|
||||
item (language.search self file symbol [] opts)]
|
||||
(if (and (not item) opts.searched-through-require)
|
||||
{:range (message.ast->range symbol file)
|
||||
:message (.. "unknown field " (tostring symbol))
|
||||
:severity message.severity.WARN
|
||||
:code 302
|
||||
:codeDescription "field checking I guess"})))))
|
||||
|
||||
(λ check [self file]
|
||||
"fill up the file.diagnostics table with linting things"
|
||||
(if self.configuration.checks.unused-definition
|
||||
(unused-definition self file))
|
||||
(if self.configuration.checks.unknown-module-field
|
||||
(unknown-module-field self file)))
|
||||
|
||||
{: check}
|
||||
1
src/fennel-ls/docs.fnl
Normal file
1
src/fennel-ls/docs.fnl
Normal file
@ -0,0 +1 @@
|
||||
{:lua54 (require :fennel-ls.docs.lua54)}
|
||||
47
src/fennel-ls/docs/lua54.fnl
Normal file
47
src/fennel-ls/docs/lua54.fnl
Normal file
@ -0,0 +1,47 @@
|
||||
(local {: sym} (require :fennel))
|
||||
{
|
||||
:_G {}
|
||||
:_VERSION {}
|
||||
:arg {}
|
||||
:assert {}
|
||||
:collectgarbage {}
|
||||
:coroutine {}
|
||||
:debug {}
|
||||
:dofile {}
|
||||
:error {}
|
||||
:getmetatable {}
|
||||
:io {}
|
||||
:ipairs {}
|
||||
:load {}
|
||||
:loadfile {}
|
||||
:math {}
|
||||
:next {}
|
||||
:os {}
|
||||
:package {}
|
||||
:pairs {:metadata {:fnl/arglist [:t]
|
||||
:fnl/docstring "If t has a metamethod __pairs, calls it with t as argument and returns the first three results from the call.
|
||||
|
||||
Otherwise, returns three values: the next function, the table t, and nil, so that the construction
|
||||
```fnl
|
||||
(each [k v (pairs t)] <body>)
|
||||
```
|
||||
will iterate over all key–value pairs of table t.
|
||||
|
||||
See function next for the caveats of modifying the table during its traversal."}}
|
||||
:pcall {}
|
||||
:print {}
|
||||
:rawequal {}
|
||||
:rawget {}
|
||||
:rawlen {}
|
||||
:rawset {}
|
||||
:require {}
|
||||
:select {}
|
||||
:setmetatable {}
|
||||
:string {}
|
||||
:table {}
|
||||
:tonumber {}
|
||||
:tostring {}
|
||||
:type {}
|
||||
:utf8 {}
|
||||
:warn {}
|
||||
:xpcall {}}
|
||||
@ -68,7 +68,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
|
||||
(λ requests.textDocument/definition [self send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri self uri)
|
||||
byte (pos->byte file.text position.line position.character)]
|
||||
byte (utils.pos->byte file.text position.line position.character)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
(symbol parents)
|
||||
;; TODO unruin this match-try
|
||||
@ -86,7 +86,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
:textDocument {: uri}
|
||||
:context {:includeDeclaration ?include-declaration?}}]
|
||||
(let [file (state.get-by-uri self uri)
|
||||
byte (pos->byte file.text line character)]
|
||||
byte (utils.pos->byte file.text line character)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
symbol
|
||||
(if (. file.definitions symbol)
|
||||
@ -107,7 +107,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
|
||||
(λ requests.textDocument/hover [self send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri self uri)
|
||||
byte (pos->byte file.text position.line position.character)]
|
||||
byte (utils.pos->byte file.text position.line position.character)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
symbol (language.search-main self file symbol {} byte)
|
||||
result {:contents (formatter.hover-format result)
|
||||
@ -152,7 +152,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(collect-scope scope :macros #{:label $ :kind kinds.Keyword} result)
|
||||
(collect-scope scope :specials #(make-completion-item self file $ scope) result))
|
||||
(icollect [_ k (ipairs file.allowed-globals) &into result]
|
||||
{:label k :kind kinds.Variable})))
|
||||
(make-completion-item self file k scope))))
|
||||
|
||||
(λ field-completion [self file symbol split]
|
||||
(case (. file.references symbol)
|
||||
@ -175,24 +175,28 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
|
||||
(λ requests.textDocument/completion [self send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri self uri)
|
||||
byte (pos->byte file.text position.line position.character)
|
||||
byte (utils.pos->byte file.text position.line position.character)
|
||||
(?symbol parents) (language.find-symbol file.ast byte)]
|
||||
(case (-?> ?symbol utils.multi-sym-split)
|
||||
(where (or nil [_ nil])) (scope-completion self file byte ?symbol parents)
|
||||
[_a _b &as split] (field-completion self file ?symbol split))))
|
||||
|
||||
|
||||
(λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}]
|
||||
(local file (state.get-by-uri self uri))
|
||||
(state.set-uri-contents self uri (apply-changes file.text contentChanges))
|
||||
(state.set-uri-contents self uri (utils.apply-changes file.text contentChanges))
|
||||
(diagnostics.check self file)
|
||||
(send (message.diagnostics file)))
|
||||
|
||||
(λ notifications.textDocument/didOpen [self send {:textDocument {: languageId : text : uri}}]
|
||||
(local file (state.set-uri-contents self uri text))
|
||||
(set file.open? true)
|
||||
(send (message.diagnostics file)))
|
||||
(diagnostics.check self file)
|
||||
(send (message.diagnostics file))
|
||||
(set file.open? true))
|
||||
|
||||
(λ notifications.textDocument/didClose [self send {:textDocument {: uri}}]
|
||||
(local file (state.get-by-uri self uri))
|
||||
(set file.open? false)
|
||||
;; TODO only reload from disk if we didn't get a didSave, instead of always
|
||||
(state.flush-uri self uri))
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ the data provided by compiler.fnl."
|
||||
(let [newfile (state.get-by-module self mod)]
|
||||
(when newfile
|
||||
(let [newitem (. newfile.ast (length newfile.ast))]
|
||||
(search self newfile newitem stack opts))))
|
||||
(search self newfile newitem stack (doto opts (tset :searched-through-require true))))))
|
||||
;; A . form indexes into item 1 with the other items
|
||||
[-dot- & split]
|
||||
(search self file (. split 1) (stack-add-split! stack split) opts)
|
||||
@ -141,14 +141,21 @@ Returns:
|
||||
(or (. file.definitions-by-scope ?scope name)
|
||||
(find-local-definition file name ?scope.parent))))
|
||||
|
||||
(λ global-info [self name]
|
||||
(. (require :fennel-ls.docs)
|
||||
self.configuration.version
|
||||
name))
|
||||
|
||||
(λ search-name-and-scope [self file name scope ?opts]
|
||||
"find a definition just from the name of the item, and the scope it is in"
|
||||
(assert (= (type name) :string))
|
||||
(let [stack (stack-add-multisym! [] name)]
|
||||
(case (. METADATA (. SPECIALS name))
|
||||
metadata {:binding (sym name) : metadata}
|
||||
_ (case (find-local-definition file name scope)
|
||||
def (search self file def.definition (stack-add-keys! stack def.keys) (or ?opts {}))))))
|
||||
_ (case (global-info self name)
|
||||
global-item global-item
|
||||
_ (case (find-local-definition file name scope)
|
||||
def (search self file def.definition (stack-add-keys! stack def.keys) (or ?opts {})))))))
|
||||
|
||||
(λ past? [?ast byte]
|
||||
;; check if a byte is past an ast object
|
||||
|
||||
@ -83,7 +83,7 @@ object."
|
||||
(assert (= (type (. default 1)) (type setting)))
|
||||
setting)
|
||||
(= :table (type default))
|
||||
(collect [k v (pairs default)]
|
||||
(collect [k _ (pairs default)]
|
||||
k (make-configuration-from-template
|
||||
(. default k)
|
||||
(?. ?user k)
|
||||
@ -93,7 +93,9 @@ object."
|
||||
(local default-configuration
|
||||
{:fennel-path (option "./?.fnl;./?/init.fnl;src/?.fnl;src/?/init.fnl")
|
||||
:macro-path (option "./?.fnl;./?/init-macros.fnl;./?/init.fnl;src/?.fnl;src/?/init-macros.fnl;src/?/init.fnl")
|
||||
:checks {:unused-definition (option true)}})
|
||||
:version (option "lua54")
|
||||
:checks {:unused-definition (option true)
|
||||
:unknown-module-field (option true)}})
|
||||
|
||||
(λ make-configuration [?c]
|
||||
(make-configuration-from-template default-configuration ?c))
|
||||
@ -107,7 +109,6 @@ object."
|
||||
(λ write-configuration [self ?configuration]
|
||||
(set self.configuration (make-configuration ?configuration)))
|
||||
|
||||
|
||||
{: flush-uri
|
||||
: get-by-module
|
||||
: get-by-uri
|
||||
|
||||
@ -25,7 +25,7 @@ These functions are all pure functions, which makes me happy."
|
||||
(λ pos->byte [str line col]
|
||||
"convert a 0-indexed line and column into a 1-indexed byte. Doesn't yet handle UTF8 UTF16 magic from the protocol"
|
||||
(var sofar 1)
|
||||
(for [i 1 line :until (not sofar)]
|
||||
(for [_ 1 line :until (not sofar)]
|
||||
(set sofar (next-line str sofar)))
|
||||
(if sofar
|
||||
(+ sofar col)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user