Refactoring / documentation comments / small fixes

This commit is contained in:
XeroOl 2022-08-24 23:12:28 -05:00
parent 33e938b8db
commit 3eeba96188
No known key found for this signature in database
GPG Key ID: 9DD4B4B4DAED0322
9 changed files with 175 additions and 67 deletions

View File

@ -1,3 +1,8 @@
"Compiler
This file is responsible for the low level tasks of analysis. Its main job
is to recieve a file object and run all of the basic analysis that will be used
later by fennel-ls.language to answer requests from the client."
(local {: sym? : list? : sequence? : sym : view &as fennel} (require :fennel))
(local message (require :fennel-ls.message))
@ -32,6 +37,7 @@
(λ compile [file]
"Compile the file, and record all the useful information from the compiler into the file object"
;; The useful information being recorded:
(let [definitions-by-scope (doto {} (setmetatable has-tables-mt))
definitions {}
diagnostics {}
@ -115,8 +121,9 @@
[-require- modname]
(tset require-calls ast true)))
(λ on-compiler-error [_ msg ast call-me-to-reset-the-compiler]
(let [range (message.ast->range ast file)]
(λ on-compile-error [_ msg ast call-me-to-reset-the-compiler]
(let [range (or (message.ast->range ast file)
(message.pos->range 0 0 0 0))]
(table.insert diagnostics
{:range range
:message msg
@ -126,6 +133,17 @@
(call-me-to-reset-the-compiler)
(error "__NOT_AN_ERROR"))
(λ on-parse-error [msg file line byte]
;; assume byte and char count is the same, ie no UTF-8
(let [range (message.pos->range line byte line byte)]
(table.insert diagnostics
{:range range
:message msg
:severity message.severity.ERROR
:code 201
:codeDescription "compiler error"}))
(error "__NOT_AN_ERROR"))
;; TODO clean up this code. It's awful now that there is error handling
(let
[plugin
@ -134,31 +152,23 @@
:symbol-to-expression reference
:call call
:destructure define
:assert-compile on-compiler-error}]
;; ATTEMPT TO PARSE AST
(match (pcall
#(icollect [ok ast (fennel.parser file.text file.uri {:plugins [plugin]})]
ast))
;; ON SUCCESS
(true ast)
(let [scope (fennel.scope)]
(each [_i form (ipairs ast)]
;; COMPILE
(match (pcall fennel.compile form {:filename file.uri
:plugins [plugin]
:allowedGlobals (icollect [k v (pairs _G)] k)
:requireAsInclude false
: scope})
(where (nil err) (not= err "__NOT_AN_ERROR"))
(error err)))
(set file.ast ast))
;; ON FAILURE
(false err)
;; RECORD THE FAILURE
(table.insert diagnostics
{:range (message.pos->range 0 0 0 0)
:message err}))
:assert-compile on-compile-error
:parse-error on-parse-error}
scope (fennel.scope)
opts {:filename file.uri
:plugins [plugin]
:allowedGlobals (icollect [k v (pairs _G)] k)
:requireAsInclude false
: scope}
parser (partial pcall (fennel.parser file.text file.uri opts))
ast (icollect [ok ok2 ast parser &until (not (and ok ok2))] ast)
_compile-output (icollect [_i form (ipairs ast)]
(match (pcall fennel.compile form opts)
(where (nil err) (not= err "__NOT_AN_ERROR"))
(table.insert diagnostics
{:range (message.pos->range 0 0 0 0)
:message err})))]
(set file.ast ast)
;; write things back to the file object

View File

@ -1,3 +1,8 @@
"Formatter
This module is for formatting code that needs to be shown to the client
in tooltips and other notification messages. It is NOT for formatting
user code."
(local {: sym
: sym?
: view
@ -20,6 +25,7 @@
(if docstring (.. "\n" docstring) "")))
(λ hover-format [result]
"Format code that will appear when the user hovers over a symbol"
(code-block
(match result.?definition
;; name + docstring

View File

@ -1,3 +1,7 @@
"Language
The high level analysis system that does deep searches following
the data provided by compiler.fnl."
(local {: sym? : list? : sequence? : sym : view} (require :fennel))
(local utils (require :fennel-ls.utils))
(local state (require :fennel-ls.state))

View File

@ -1,3 +1,10 @@
"State
This module keeps track of the state of the language server.
There are helpers to get file objects, and in the future, there
will be functions for managing user options. There is no global
state in this project: all state will be stored in the \"self\"
object."
(local utils (require :fennel-ls.utils))
(local searcher (require :fennel-ls.searcher))
(local {: compile} (require :fennel-ls.compiler))

View File

@ -75,14 +75,18 @@ These functions are all pure functions, which makes me happy."
(or (?. (getmetatable ?ast) info)
(. ?ast info)))
(fn multi-sym-split [sym ?offset]
(local sym (tostring sym))
(local offset (or ?offset (length sym)))
(local next-separator (or (sym:find ".[%.:]" offset)
(length sym)))
(local sym (sym:sub 1 next-separator))
(icollect [word (: (.. sym ".") :gmatch "(.-)[%.:]")]
word))
(fn multi-sym-split [symbol ?offset]
(local symbol (tostring symbol))
(if (or (= symbol ".")
(= symbol "..")
(= symbol "..."))
[symbol]
(let [offset (or ?offset (length symbol))
next-separator (or (symbol:find ".[.:]" offset)
(length symbol))
symbol (symbol:sub 1 next-separator)]
(icollect [word (: (.. symbol ".") :gmatch "(.-)[.:]")]
word))))
(λ type= [val typ]
(= (type val) typ))

36
test/completion-test.fnl Normal file
View File

@ -0,0 +1,36 @@
(import-macros {: is-matching : describe : it : before-each} :test)
(local is (require :luassert))
(local {: view} (require :fennel))
(local {: ROOT-URI
: setup-server} (require :test.util))
(local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message))
(local FILENAME (.. ROOT-URI "imaginary-file.fnl"))
(fn open-file [state text]
(dispatch.handle* state
(message.create-notification "textDocument/didOpen"
{:textDocument
{:uri FILENAME
:languageId "fennel"
:version 1
: text}})))
(describe "completions")
;; (it "suggests globals"
;; (local state (doto [] setup-server))
;; ;; empty file
;; (open-file state "")
;; (let [response (dispatch.handle* state
;; (message.create-request 2 "textDocument/completion"
;; {:position {:line 0 :character 0}
;; :textDocument {:uri FILENAME}}))]
;; (is-matching response nil "oops"))))
;; (it "suggests locals in scope")
;; (it "does not suggest locals out of scope")
;; (it "suggests fields of tables")
;; (it "knows what fields are meant to be inside of globals")

View File

@ -8,35 +8,69 @@
(local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message))
(macro find [t body ?sentinel]
(assert-compile (not ?sentinel) "you can only have one thing here, put a `(do)`")
(assert-compile (sequence? t) "[] square brackets please")
(local result (gensym :result))
(local nil* (sym :nil))
(table.insert t 1 result)
(table.insert t 2 nil*)
(table.insert t `&until)
(table.insert t result)
`(accumulate ,t ,body))
(fn open-file [state text]
(dispatch.handle* state
(message.create-notification "textDocument/didOpen"
{:textDocument
{:uri (.. ROOT-URI "imaginary-file.fnl")
:languageId "fennel"
:version 1
: text}})))
(describe "diagnostic messages"
(it "handles compile errors"
(local state (doto [] setup-server))
(let
[responses
(dispatch.handle* state
(message.create-notification "textDocument/didOpen"
{:textDocument
{:uri (.. ROOT-URI "imaginary-file.fnl")
:languageId "fennel"
:version 1
:text "(do do)"}}))]
(is-matching
responses
[{:params {:diagnostics [diagnostic]}}]
"")))
(let [responses (open-file state "(do do)")
diagnostic
(match responses
[{:params {: diagnostics}}]
(find [i v (ipairs diagnostics)]
(match v
{:message "tried to reference a special form at runtime"
:range {:start {:character 4 :line 0}
:end {:character 6 :line 0}}}
v)))]
(is diagnostic "expected a diagnostic")))
(it "handles parse errors"
(local state (doto [] setup-server))
(let
[responses
(dispatch.handle* state
(message.create-notification "textDocument/didOpen"
{:textDocument
{:uri (.. ROOT-URI "imaginary-file.fnl")
:languageId "fennel"
:version 1
:text "(do (print :hello(]"}}))]
(is-matching
responses
[{:params {:diagnostics [diagnostic]}}]
""))))
(let [responses (open-file state "(do (print :hello(]")
diagnostic
(match responses
[{:params {: diagnostics}}]
(find [i v (ipairs diagnostics)]
(match v
{:message "expected whitespace before opening delimiter ("
:range {:start {:character 17 :line 1}
:end {:character 17 :line 1}}}
v)))]
(is diagnostic "expected a diagnostic")))
(it "handles (match)"
(local state (doto [] setup-server))
(let [responses (open-file state "(match)")]
(is-matching responses
[{:params
{:diagnostics
[{:range {:start {:character a :line b}
:end {:character c :line d}}}]}}]
"diagnostics should always have a range"))))
;; TODO lints:
;; unnecessary (do) in body position
;; Unused variables / fields (maybe difficult)
;; discarding results to various calls
;; unnecessary `do`/`values` with only one inner form
;; mark when unification is happening on a `match` pattern (may be difficult)
;; think of more lints

View File

@ -70,12 +70,18 @@
;; TODO
;; (it "can go to a function in another file imported via destructuring assignment") ;; WORKS, just needs a test case
;; (it "doesn't have ghost definitions from the same byte ranges as the macro files it's using") ;; Unconfirmed, I saw some weird behavior a while ago
;; (it "can go through more than one extra file")
;; (it "will give up instead of freezing on recursive requires")
;; (it "finds the definition of in-file macros")
;; (it "can follow import-macros (destructuring)")
;; (it "can follow import-macros (namespaced)")
;; (it "can go to the definition even in a lua file")
;; (it "can go to a function's arguments when they're available")
;; (it "finds (fn a.b [] ...) declarations")
;; (it "finds (set a.b) definitions")
;; (it "finds (tset a :b) definitions")
;; (it "finds (setmetatable a {__index {:b def}) definitions")
;; (it "finds definitions from inside a function (fn foo [] (local x 10) {: x}) (let [result (foo)] (print result.x)) finds result.x")
;; (it "finds basic setmetatable definitions with an __index function")
;; (it "can return to callsite and go through a function's arguments when they're available")
;; (it "can go to a function's reference OR read type inference comments when callsite isn't available (PICK ONE)")
;; (it "can work with a custom fennelpath") ;; Wait until an options system is done

View File

@ -1,9 +1,10 @@
((require :busted.runner))
(require :test.json-rpc-test)
(require :test.string-processing-test)
(require :test.lsp-test)
(require :test.completion-test)
(require :test.diagnostic-test)
(require :test.goto-definition-test)
(require :test.hover-test)
(require :test.json-rpc-test)
(require :test.lsp-test)
(require :test.misc-test)
(require :test.diagnostic-test)
(require :test.string-processing-test)