Refactoring
This commit is contained in:
parent
4847179afd
commit
42850a76e0
@ -9,6 +9,8 @@
|
|||||||
(fn contains? [ast byte]
|
(fn contains? [ast byte]
|
||||||
"check if a byte is in range of the AST object"
|
"check if a byte is in range of the AST object"
|
||||||
(and (= (type ast) :table)
|
(and (= (type ast) :table)
|
||||||
|
(get-ast-info ast :bytestart)
|
||||||
|
(get-ast-info ast :byteend)
|
||||||
(<= (get-ast-info ast :bytestart)
|
(<= (get-ast-info ast :bytestart)
|
||||||
byte
|
byte
|
||||||
(get-ast-info ast :byteend))))
|
(get-ast-info ast :byteend))))
|
||||||
@ -16,7 +18,9 @@
|
|||||||
(fn past? [ast byte]
|
(fn past? [ast byte]
|
||||||
"check if a byte is past the range of the AST object"
|
"check if a byte is past the range of the AST object"
|
||||||
(and (= (type ast) :table)
|
(and (= (type ast) :table)
|
||||||
(< byte (get-ast-info ast :bytestart))))
|
(get-ast-info ast :bytestart)
|
||||||
|
(< byte (get-ast-info ast :bytestart))
|
||||||
|
false))
|
||||||
|
|
||||||
(fn range [text ast]
|
(fn range [text ast]
|
||||||
"create a LSP range representing the span of an AST object"
|
"create a LSP range representing the span of an AST object"
|
||||||
@ -24,7 +28,7 @@
|
|||||||
(match (values (get-ast-info ast :bytestart) (get-ast-info ast :byteend))
|
(match (values (get-ast-info ast :bytestart) (get-ast-info ast :byteend))
|
||||||
(i j)
|
(i j)
|
||||||
(let [(start-line start-col) (util.byte->pos text i)
|
(let [(start-line start-col) (util.byte->pos text i)
|
||||||
(end-line end-col) (util.byte->pos text j)]
|
(end-line end-col) (util.byte->pos text (+ j 1))]
|
||||||
{:start {:line start-line :character start-col}
|
{:start {:line start-line :character start-col}
|
||||||
:end {:line end-line :character end-col}}))))
|
:end {:line end-line :character end-col}}))))
|
||||||
|
|
||||||
|
|||||||
@ -4,17 +4,18 @@
|
|||||||
;; words surrounded by - are symbols,
|
;; words surrounded by - are symbols,
|
||||||
;; because fennel doesn't allow 'require in a runtime file
|
;; because fennel doesn't allow 'require in a runtime file
|
||||||
(local -require- (fennel.sym :require))
|
(local -require- (fennel.sym :require))
|
||||||
(local -local- (fennel.sym :local))
|
|
||||||
(local -fn- (fennel.sym :fn))
|
(local -fn- (fennel.sym :fn))
|
||||||
|
(local -λ- (fennel.sym :λ))
|
||||||
|
(local -lambda- (fennel.sym :lambda))
|
||||||
|
|
||||||
;; types of things in the file.references list
|
;; types of things in the file.references list
|
||||||
{:from "a literal range" :to "a literal range"}
|
{:from "a literal range" :to "a literal range"}
|
||||||
{:from "a literal range" :to-other-module ["modname" "key1" "key2" "key3" "key4" "etc"]}
|
{:from "a literal range" :to-other-module ["modname" "key1" "key2" "key3" "key4" "etc"]}
|
||||||
|
|
||||||
(fn table? [t]
|
(λ table? [t]
|
||||||
(= :table (type t)))
|
(= :table (type t)))
|
||||||
|
|
||||||
(fn multisym? [t]
|
(λ multisym? [t]
|
||||||
(and (fennel.sym? t)
|
(and (fennel.sym? t)
|
||||||
(let [t (tostring t)]
|
(let [t (tostring t)]
|
||||||
(or (t:find "%.")
|
(or (t:find "%.")
|
||||||
@ -30,54 +31,70 @@
|
|||||||
(doto {}
|
(doto {}
|
||||||
(setmetatable
|
(setmetatable
|
||||||
{:__index
|
{:__index
|
||||||
(fn [self key]
|
(λ [self key]
|
||||||
(let [val {}]
|
(let [val {}]
|
||||||
(tset self key val)
|
(tset self key val)
|
||||||
val))})))
|
val))})))
|
||||||
|
|
||||||
(fn find-reference [name scope]
|
(λ find-reference [name ?scope]
|
||||||
(when scope
|
(when ?scope
|
||||||
(or (. scope-notes scope (tostring name))
|
(or (. scope-notes ?scope (tostring name))
|
||||||
(find-reference name scope.parent))))
|
(find-reference name ?scope.parent))))
|
||||||
|
|
||||||
(fn call [ast scope]
|
(λ reference [ast scope]
|
||||||
"called for every function call. Most calls aren't interesting, but (require) and (local) are"
|
|
||||||
(match ast
|
|
||||||
[-local- name value]
|
|
||||||
nil ;; not actually interesting, I pranked you
|
|
||||||
(where [-require- mod] (= :string (type mod)))
|
|
||||||
(insert file.references {:from ast :to-other-module [mod]})))
|
|
||||||
;; nothing
|
|
||||||
|
|
||||||
(fn reference [ast scope]
|
|
||||||
"called whenever a variable is referenced"
|
"called whenever a variable is referenced"
|
||||||
(assert (fennel.sym? ast))
|
(assert (fennel.sym? ast))
|
||||||
(let [name (or (string.match (tostring ast) "[^%.:]+"))]
|
(let [name (string.match (tostring ast) "[^%.:]+")]
|
||||||
(table.insert file.references {:from ast :to (find-reference name scope)})))
|
(table.insert file.references {:from ast :to (find-reference name scope)})))
|
||||||
|
|
||||||
(fn fn* [ast scope]
|
(λ define [?definition binding scope]
|
||||||
(match ast
|
|
||||||
(where [-fn- name args]
|
|
||||||
(and (fennel.sym? name)
|
|
||||||
(not (multisym? name))
|
|
||||||
(table? args)))
|
|
||||||
(tset (. scope-notes scope.parent) (tostring name) ast)))
|
|
||||||
;; (each [_ argument (ipairs args)]))
|
|
||||||
|
|
||||||
(fn define [definition binding scope]
|
|
||||||
"called whenever a local variable or destructure statement is introduced"
|
"called whenever a local variable or destructure statement is introduced"
|
||||||
(when (fennel.sym? binding) ;; for now, I am going to bury my head in the sand and ignore destructure logic
|
(when (fennel.sym? binding) ;; for now, I am going to bury my head in the sand and ignore destructure logic
|
||||||
(tset (. scope-notes scope) (tostring binding) binding)))
|
(tset (. scope-notes scope) (tostring binding) binding)))
|
||||||
|
|
||||||
|
(λ define-function-name [ast scope]
|
||||||
|
(match ast
|
||||||
|
(where [_fn name args]
|
||||||
|
(and (fennel.sym? name)
|
||||||
|
(not (multisym? name)) ;; not dealing with multisym for now
|
||||||
|
(fennel.sequence? args)))
|
||||||
|
(tset (. scope-notes scope.parent) (tostring name) ast)))
|
||||||
|
|
||||||
|
(λ define-function-args [ast scope]
|
||||||
|
(local args
|
||||||
|
(match ast
|
||||||
|
(where [_fn args] (fennel.sequence? args)) args
|
||||||
|
(where [_fn _name args] (fennel.sequence? args)) args))
|
||||||
|
(each [_ argument (ipairs args)]
|
||||||
|
(define nil argument scope))) ;; we say arguments are bound to "nil" for now
|
||||||
|
|
||||||
|
(λ define-function [ast scope]
|
||||||
|
"Introduces the various symbols exported by a function.
|
||||||
|
This cannot be done through the :fn feature of the compiler plugin system, because it needs to be
|
||||||
|
called before the body of the function happens"
|
||||||
|
(define-function-name ast scope)
|
||||||
|
(define-function-args ast scope))
|
||||||
|
|
||||||
|
(λ call [ast scope]
|
||||||
|
"called for every function call. Most calls aren't interesting, but (require) and (local) are"
|
||||||
|
(match ast
|
||||||
|
(where [-require- mod] (= :string (type mod)))
|
||||||
|
(insert file.references {:from ast :to-other-module [mod]})
|
||||||
|
[-fn-]
|
||||||
|
(define-function ast scope)
|
||||||
|
[-λ-]
|
||||||
|
(define-function ast scope)
|
||||||
|
[-lambda-]
|
||||||
|
(define-function ast scope)))
|
||||||
|
|
||||||
(local plugin
|
(local plugin
|
||||||
{:name "fennel-ls"
|
{:name "fennel-ls"
|
||||||
:versions ["1.2.0"]
|
:versions ["1.2.0"]
|
||||||
:symbol-to-expression reference
|
:symbol-to-expression reference
|
||||||
: call
|
: call
|
||||||
:fn fn*
|
|
||||||
:destructure define})
|
:destructure define})
|
||||||
|
|
||||||
(fennel.compileString file.text
|
(pcall fennel.compileString file.text
|
||||||
{:filename file.uri
|
{:filename file.uri
|
||||||
:plugins [plugin]}))
|
:plugins [plugin]}))
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,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}}]
|
||||||
(local file (state.get-by-uri self uri))
|
(local file (state.get-by-uri self uri))
|
||||||
(local byte (util.pos->byte file.text position.line position.character))
|
(local byte (util.pos->byte file.text position.line position.character))
|
||||||
|
(log file)
|
||||||
(accumulate [result nil
|
(accumulate [result nil
|
||||||
_ reference (ipairs file.references) &until (or result (parser.past? reference.from byte))]
|
_ reference (ipairs file.references) &until (or result (parser.past? reference.from byte))]
|
||||||
(if (parser.contains? reference.from byte)
|
(if (parser.contains? reference.from byte)
|
||||||
|
|||||||
@ -10,57 +10,58 @@
|
|||||||
|
|
||||||
(describe "jump to definition"
|
(describe "jump to definition"
|
||||||
|
|
||||||
(var state nil)
|
(fn check [request-file line char response-file start-line start-col end-line end-col]
|
||||||
|
(local state (doto [] setup-server))
|
||||||
(before-each
|
(let [message (dispatch.handle* state
|
||||||
(set state [])
|
|
||||||
(setup-server state))
|
|
||||||
|
|
||||||
(fn request-definition-at [line char file]
|
|
||||||
(message.create-request 2 "textDocument/definition"
|
(message.create-request 2 "textDocument/definition"
|
||||||
{:position {:character char :line line}
|
{:position {:character char :line line}
|
||||||
:textDocument {:uri (.. ROOT-URI "/" file)}}))
|
:textDocument {:uri (.. ROOT-URI "/" request-file)}}))
|
||||||
|
uri (.. ROOT-URI "/" response-file)]
|
||||||
|
(assert-matches
|
||||||
|
message
|
||||||
|
[{:jsonrpc "2.0" :id 2
|
||||||
|
:result {: uri
|
||||||
|
:range {:start {:line start-line :character start-col}
|
||||||
|
:end {:line end-line :character end-col}}}}]
|
||||||
|
(.. "expected position: " start-line " " start-col " " end-line " " end-col))))
|
||||||
|
|
||||||
(it "handles (local _ (require XXX)"
|
(it "handles (local _ (require XXX)"
|
||||||
(local uri (.. ROOT-URI "/" "foo.fnl"))
|
(check "example.fnl" 0 11 "foo.fnl" 0 0 0 0))
|
||||||
(assert-matches
|
|
||||||
(dispatch.handle* state (request-definition-at 0 11 "example.fnl"))
|
|
||||||
[{:jsonrpc "2.0" :id 2
|
|
||||||
:result {: uri :range {:start {:line 0 :character 0}
|
|
||||||
:end {:line 0 :character 0}}}}]))
|
|
||||||
|
|
||||||
(it "handles (require XXX))"
|
(it "handles (require XXX))"
|
||||||
(local uri (.. ROOT-URI "/" "bar.fnl"))
|
(check "example.fnl" 1 5 "bar.fnl" 0 0 0 0))
|
||||||
(assert-matches
|
|
||||||
(dispatch.handle* state (request-definition-at 1 5 "example.fnl"))
|
|
||||||
[{:jsonrpc "2.0" :id 2
|
|
||||||
:result {: uri :range {:start {:line 0 :character 0}
|
|
||||||
:end {:line 0 :character 0}}}}]))
|
|
||||||
|
|
||||||
(it "can go to a fn"
|
(it "can go to a fn"
|
||||||
(local uri (.. ROOT-URI "/" "example.fnl"))
|
;; TODO maybe it's better to just go to the name of the function, not the whole list
|
||||||
(assert-matches
|
(check "example.fnl" 9 3 "example.fnl" 4 0 7 20))
|
||||||
(dispatch.handle* state (request-definition-at 9 3 "example.fnl"))
|
|
||||||
[{:jsonrpc "2.0" :id 2
|
|
||||||
:result {: uri :range {:start {:line 4 :character 0}
|
|
||||||
:end {:line 7 :character 19}}}}])))
|
|
||||||
|
|
||||||
;; (it "can open a require with a custom fennelpath")
|
(it "can go to a local"
|
||||||
;; (it "can go to a fn")
|
(check "example.fnl" 7 17 "example.fnl" 6 9 6 10))
|
||||||
;; (it "can go to a local")
|
|
||||||
;; (it "can go to a table and its field")
|
(it "can go to a function argument"
|
||||||
|
(check "example.fnl" 5 9 "example.fnl" 4 9 4 10))
|
||||||
|
|
||||||
|
(it "can handle variables shadowed with let"
|
||||||
|
(check "example.fnl" 14 10 "example.fnl" 13 6 13 9))
|
||||||
|
|
||||||
|
(it "can sort out the unification rule with match (variable unified)"
|
||||||
|
(check "example.fnl" 19 12 "example.fnl" 17 8 17 9))
|
||||||
|
|
||||||
|
(it "can sort out the unification rule with match (variable introduced)"
|
||||||
|
(check "example.fnl" 20 12 "example.fnl" 20 9 20 10)))
|
||||||
|
|
||||||
|
;; (it "doesn't have ghost definitions from the same byte ranges as the macro files it's using")
|
||||||
|
;; (it "can go to a reference that occurs in a macro")
|
||||||
|
;; (it "can go to a function inside a table")
|
||||||
|
;; (it "can go to an field inside of a table")
|
||||||
;; (it "can go to a destructured local")
|
;; (it "can go to a destructured local")
|
||||||
;; (it "can go to a table field in another file")
|
;; (it "can go to a destructured function argument")
|
||||||
;; (it "can go to a table field in another file (through a destructuring assignment)")
|
;; (it "can go to a function in another file when accessed by multisym")
|
||||||
;; (it "can go to a field in a lua file")
|
;; (it "can go to a function in another file imported via destructuring assignment")
|
||||||
;; (it "finds the definition of macros")
|
;; (it "can work with a custom fennelpath")
|
||||||
;; (it "can go through more than one extra file")
|
;; (it "can go through more than one extra file")
|
||||||
;; (it "will give up on recursive requires")
|
;; (it "will give up instead of freezing on recursive requires")
|
||||||
|
;; (it "does slightly better in the presense of macros")
|
||||||
|
;; (it "finds the definition of macros")
|
||||||
;; (it "can follow import-macros")
|
;; (it "can follow import-macros")
|
||||||
|
;; (it "can go to the definition even in a lua file")
|
||||||
;; (describe "diagnostic")
|
|
||||||
;; (it "reports compiler errors")
|
|
||||||
;; (it "reports lint warnings")
|
|
||||||
|
|
||||||
;; (describe "completion")
|
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
(local {: ROOT-PATH : ROOT-URI} (require :test.util))
|
(local {: ROOT-PATH : ROOT-URI} (require :test.util))
|
||||||
(local dispatch (require :fennel-ls.dispatch))
|
(local dispatch (require :fennel-ls.dispatch))
|
||||||
|
|
||||||
|
|
||||||
(local server-initialize-message
|
(local server-initialize-message
|
||||||
{:id 1
|
{:id 1
|
||||||
:jsonrpc "2.0"
|
:jsonrpc "2.0"
|
||||||
@ -27,4 +26,3 @@
|
|||||||
[{:jsonrpc "2.0" :id 1
|
[{:jsonrpc "2.0" :id 1
|
||||||
:result {:capabilities {}
|
:result {:capabilities {}
|
||||||
:serverInfo {:name "fennel-ls" : version}}}])))
|
:serverInfo {:name "fennel-ls" : version}}}])))
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
(fn [] ,...)))
|
(fn [] ,...)))
|
||||||
|
|
||||||
|
|
||||||
(fn assert-matches [item pattern]
|
(fn assert-matches [item pattern ?msg]
|
||||||
"check if item matches a pattern according to fennel's `match` builtin"
|
"check if item matches a pattern according to fennel's `match` builtin"
|
||||||
`(match ,item
|
`(match ,item
|
||||||
,pattern nil
|
,pattern nil
|
||||||
@ -26,7 +26,8 @@
|
|||||||
(let [fennel# (require :fennel)]
|
(let [fennel# (require :fennel)]
|
||||||
(fennel#.view ?otherwise#))
|
(fennel#.view ?otherwise#))
|
||||||
"\ndid not match pattern:\n"
|
"\ndid not match pattern:\n"
|
||||||
,(view pattern)))))
|
,(view pattern)
|
||||||
|
(and ,?msg (.. "\n" ,?msg))))))
|
||||||
|
|
||||||
{: it
|
{: it
|
||||||
: describe
|
: describe
|
||||||
|
|||||||
@ -11,4 +11,13 @@
|
|||||||
|
|
||||||
(print bazfn)
|
(print bazfn)
|
||||||
|
|
||||||
|
(let [bar "shadowed"]
|
||||||
|
(print bar))
|
||||||
|
|
||||||
|
(λ test [{: foo}]
|
||||||
|
(let [a 10]
|
||||||
|
(match [0 10]
|
||||||
|
[1 a] a
|
||||||
|
[0 b] b)))
|
||||||
|
|
||||||
{: bar}
|
{: bar}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user