New "unknown module field" diagnostic, and the groundwork for global completions with documentation

This commit is contained in:
XeroOl 2023-06-05 19:49:42 -05:00
parent 20b9094592
commit 8001c91120
8 changed files with 112 additions and 32 deletions

View File

@ -234,7 +234,7 @@ later by fennel-ls.language to answer requests from the client."
(error "__NOT_AN_ERROR"))) (error "__NOT_AN_ERROR")))
(local allowed-globals (local allowed-globals
(icollect [k v (pairs _G)] (icollect [k _ (pairs _G)]
k)) k))
(table.insert allowed-globals :vim) (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) ; {:range (message.pos->range 0 0 0 0)
; :message (.. "unrecoverable compiler error: " err)}) ; :message (.. "unrecoverable compiler error: " err)})
;; analyze more things
;; write things back to the file object
(local deep-references {})
; (each [sym target (pairs references)] ; (each [sym target (pairs references)]
; (if ; (if
; (sym? target) ; (sym? target)
@ -284,18 +280,6 @@ later by fennel-ls.language to answer requests from the client."
; (= :table (type target)))) ; (= :table (type target))))
; ;; base case??? ; ;; 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.ast ast)
(set file.scope scope) (set file.scope scope)
(set file.scopes scopes) (set file.scopes scopes)

View 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
View File

@ -0,0 +1 @@
{:lua54 (require :fennel-ls.docs.lua54)}

View 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 keyvalue 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 {}}

View File

@ -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}}] (λ requests.textDocument/definition [self send {: position :textDocument {: uri}}]
(let [file (state.get-by-uri self 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) (case-try (language.find-symbol file.ast byte)
(symbol parents) (symbol parents)
;; TODO unruin this match-try ;; 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} :textDocument {: uri}
:context {:includeDeclaration ?include-declaration?}}] :context {:includeDeclaration ?include-declaration?}}]
(let [file (state.get-by-uri self uri) (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) (case-try (language.find-symbol file.ast byte)
symbol symbol
(if (. file.definitions 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}}] (λ requests.textDocument/hover [self send {: position :textDocument {: uri}}]
(let [file (state.get-by-uri self 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) (case-try (language.find-symbol file.ast byte)
symbol (language.search-main self file symbol {} byte) symbol (language.search-main self file symbol {} byte)
result {:contents (formatter.hover-format result) 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 :macros #{:label $ :kind kinds.Keyword} result)
(collect-scope scope :specials #(make-completion-item self file $ scope) result)) (collect-scope scope :specials #(make-completion-item self file $ scope) result))
(icollect [_ k (ipairs file.allowed-globals) &into 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] (λ field-completion [self file symbol split]
(case (. file.references symbol) (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}}] (λ requests.textDocument/completion [self send {: position :textDocument {: uri}}]
(let [file (state.get-by-uri self 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)] (?symbol parents) (language.find-symbol file.ast byte)]
(case (-?> ?symbol utils.multi-sym-split) (case (-?> ?symbol utils.multi-sym-split)
(where (or nil [_ nil])) (scope-completion self file byte ?symbol parents) (where (or nil [_ nil])) (scope-completion self file byte ?symbol parents)
[_a _b &as split] (field-completion self file ?symbol split)))) [_a _b &as split] (field-completion self file ?symbol split))))
(λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}] (λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}]
(local file (state.get-by-uri self 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))) (send (message.diagnostics file)))
(λ notifications.textDocument/didOpen [self send {:textDocument {: languageId : text : uri}}] (λ notifications.textDocument/didOpen [self send {:textDocument {: languageId : text : uri}}]
(local file (state.set-uri-contents self uri text)) (local file (state.set-uri-contents self uri text))
(set file.open? true) (diagnostics.check self file)
(send (message.diagnostics file))) (send (message.diagnostics file))
(set file.open? true))
(λ notifications.textDocument/didClose [self send {:textDocument {: uri}}] (λ notifications.textDocument/didClose [self send {:textDocument {: uri}}]
(local file (state.get-by-uri self 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 ;; TODO only reload from disk if we didn't get a didSave, instead of always
(state.flush-uri self uri)) (state.flush-uri self uri))

View File

@ -65,7 +65,7 @@ the data provided by compiler.fnl."
(let [newfile (state.get-by-module self mod)] (let [newfile (state.get-by-module self mod)]
(when newfile (when newfile
(let [newitem (. newfile.ast (length newfile.ast))] (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 ;; A . form indexes into item 1 with the other items
[-dot- & split] [-dot- & split]
(search self file (. split 1) (stack-add-split! stack split) opts) (search self file (. split 1) (stack-add-split! stack split) opts)
@ -141,14 +141,21 @@ Returns:
(or (. file.definitions-by-scope ?scope name) (or (. file.definitions-by-scope ?scope name)
(find-local-definition file name ?scope.parent)))) (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] (λ 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" "find a definition just from the name of the item, and the scope it is in"
(assert (= (type name) :string)) (assert (= (type name) :string))
(let [stack (stack-add-multisym! [] name)] (let [stack (stack-add-multisym! [] name)]
(case (. METADATA (. SPECIALS name)) (case (. METADATA (. SPECIALS name))
metadata {:binding (sym name) : metadata} metadata {:binding (sym name) : metadata}
_ (case (find-local-definition file name scope) _ (case (global-info self name)
def (search self file def.definition (stack-add-keys! stack def.keys) (or ?opts {})))))) 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] (λ past? [?ast byte]
;; check if a byte is past an ast object ;; check if a byte is past an ast object

View File

@ -83,7 +83,7 @@ object."
(assert (= (type (. default 1)) (type setting))) (assert (= (type (. default 1)) (type setting)))
setting) setting)
(= :table (type default)) (= :table (type default))
(collect [k v (pairs default)] (collect [k _ (pairs default)]
k (make-configuration-from-template k (make-configuration-from-template
(. default k) (. default k)
(?. ?user k) (?. ?user k)
@ -93,7 +93,9 @@ object."
(local default-configuration (local default-configuration
{:fennel-path (option "./?.fnl;./?/init.fnl;src/?.fnl;src/?/init.fnl") {: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") :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 [?c]
(make-configuration-from-template default-configuration ?c)) (make-configuration-from-template default-configuration ?c))
@ -107,7 +109,6 @@ object."
(λ write-configuration [self ?configuration] (λ write-configuration [self ?configuration]
(set self.configuration (make-configuration ?configuration))) (set self.configuration (make-configuration ?configuration)))
{: flush-uri {: flush-uri
: get-by-module : get-by-module
: get-by-uri : get-by-uri

View File

@ -25,7 +25,7 @@ These functions are all pure functions, which makes me happy."
(λ pos->byte [str line col] (λ 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" "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) (var sofar 1)
(for [i 1 line :until (not sofar)] (for [_ 1 line :until (not sofar)]
(set sofar (next-line str sofar))) (set sofar (next-line str sofar)))
(if sofar (if sofar
(+ sofar col) (+ sofar col)