mirror of
https://github.com/janet-lang/janet
synced 2025-10-10 05:17:43 +00:00
Refactor flycheck to allow customization. Address #1638
Bindings can define their own flycheckers in a simple fashion.
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
###
|
###
|
||||||
###
|
###
|
||||||
|
|
||||||
(def defn :macro
|
(def defn :macro :flycheck
|
||||||
```
|
```
|
||||||
(defn name & more)
|
(defn name & more)
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
# Build return value
|
# Build return value
|
||||||
~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start)))))
|
~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start)))))
|
||||||
|
|
||||||
(defn defmacro :macro
|
(defn defmacro :macro :flycheck
|
||||||
"Define a macro."
|
"Define a macro."
|
||||||
[name & more]
|
[name & more]
|
||||||
(setdyn name @{}) # override old macro definitions in the case of a recursive macro
|
(setdyn name @{}) # override old macro definitions in the case of a recursive macro
|
||||||
@@ -57,12 +57,12 @@
|
|||||||
[f & args]
|
[f & args]
|
||||||
(f ;args))
|
(f ;args))
|
||||||
|
|
||||||
(defmacro defmacro-
|
(defmacro defmacro- :flycheck
|
||||||
"Define a private macro that will not be exported."
|
"Define a private macro that will not be exported."
|
||||||
[name & more]
|
[name & more]
|
||||||
(apply defn name :macro :private more))
|
(apply defn name :macro :private more))
|
||||||
|
|
||||||
(defmacro defn-
|
(defmacro defn- :flycheck
|
||||||
"Define a private function that will not be exported."
|
"Define a private function that will not be exported."
|
||||||
[name & more]
|
[name & more]
|
||||||
(apply defn name :private more))
|
(apply defn name :private more))
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
(defmacro /= "Shorthand for (set x (/ x n))." [x & ns] ~(set ,x (,/ ,x ,;ns)))
|
(defmacro /= "Shorthand for (set x (/ x n))." [x & ns] ~(set ,x (,/ ,x ,;ns)))
|
||||||
(defmacro %= "Shorthand for (set x (% x n))." [x & ns] ~(set ,x (,% ,x ,;ns)))
|
(defmacro %= "Shorthand for (set x (% x n))." [x & ns] ~(set ,x (,% ,x ,;ns)))
|
||||||
|
|
||||||
(defmacro assert
|
(defmacro assert :flycheck # should top level assert flycheck?
|
||||||
"Throw an error if x is not truthy. Will not evaluate `err` if x is truthy."
|
"Throw an error if x is not truthy. Will not evaluate `err` if x is truthy."
|
||||||
[x &opt err]
|
[x &opt err]
|
||||||
(def v (gensym))
|
(def v (gensym))
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
,v
|
,v
|
||||||
(,error ,(if err err (string/format "assert failure in %j" x))))))
|
(,error ,(if err err (string/format "assert failure in %j" x))))))
|
||||||
|
|
||||||
(defmacro defdyn
|
(defmacro defdyn :flycheck
|
||||||
``Define an alias for a keyword that is used as a dynamic binding. The
|
``Define an alias for a keyword that is used as a dynamic binding. The
|
||||||
alias is a normal, lexically scoped binding that can be used instead of
|
alias is a normal, lexically scoped binding that can be used instead of
|
||||||
a keyword to prevent typos. `defdyn` does not set dynamic bindings or otherwise
|
a keyword to prevent typos. `defdyn` does not set dynamic bindings or otherwise
|
||||||
@@ -171,6 +171,9 @@
|
|||||||
(defdyn *macro-form*
|
(defdyn *macro-form*
|
||||||
"Inside a macro, is bound to the source form that invoked the macro")
|
"Inside a macro, is bound to the source form that invoked the macro")
|
||||||
|
|
||||||
|
(defdyn *flychecking*
|
||||||
|
"Check if the current form is being evaluated inside `flycheck`. Will be `true` while flychecking.")
|
||||||
|
|
||||||
(defdyn *lint-error*
|
(defdyn *lint-error*
|
||||||
"The current lint error level. The error level is the lint level at which compilation will exit with an error and not continue.")
|
"The current lint error level. The error level is the lint level at which compilation will exit with an error and not continue.")
|
||||||
|
|
||||||
@@ -2354,7 +2357,7 @@
|
|||||||
|
|
||||||
(set macexvar macex)
|
(set macexvar macex)
|
||||||
|
|
||||||
(defmacro varfn
|
(defmacro varfn :flycheck
|
||||||
``Create a function that can be rebound. `varfn` has the same signature
|
``Create a function that can be rebound. `varfn` has the same signature
|
||||||
as `defn`, but defines functions in the environment as vars. If a var `name`
|
as `defn`, but defines functions in the environment as vars. If a var `name`
|
||||||
already exists in the environment, it is rebound to the new function. Returns
|
already exists in the environment, it is rebound to the new function. Returns
|
||||||
@@ -3945,7 +3948,7 @@
|
|||||||
[& forms]
|
[& forms]
|
||||||
(def state (gensym))
|
(def state (gensym))
|
||||||
(def loaded (gensym))
|
(def loaded (gensym))
|
||||||
~((fn []
|
~((fn :delay []
|
||||||
(var ,state nil)
|
(var ,state nil)
|
||||||
(var ,loaded nil)
|
(var ,loaded nil)
|
||||||
(fn []
|
(fn []
|
||||||
@@ -3977,7 +3980,7 @@
|
|||||||
:lazy lazy
|
:lazy lazy
|
||||||
:map-symbols map-symbols}))
|
:map-symbols map-symbols}))
|
||||||
|
|
||||||
(defmacro ffi/defbind-alias
|
(defmacro ffi/defbind-alias :flycheck
|
||||||
"Generate bindings for native functions in a convenient manner.
|
"Generate bindings for native functions in a convenient manner.
|
||||||
Similar to defbind but allows for the janet function name to be
|
Similar to defbind but allows for the janet function name to be
|
||||||
different than the FFI function."
|
different than the FFI function."
|
||||||
@@ -3988,6 +3991,8 @@
|
|||||||
(def formal-args (map 0 arg-pairs))
|
(def formal-args (map 0 arg-pairs))
|
||||||
(def type-args (map 1 arg-pairs))
|
(def type-args (map 1 arg-pairs))
|
||||||
(def computed-type-args (eval ~[,;type-args]))
|
(def computed-type-args (eval ~[,;type-args]))
|
||||||
|
(if (dyn *flychecking*)
|
||||||
|
(break ~(defn ,alias ,;meta [,;formal-args] nil)))
|
||||||
(def {:native lib
|
(def {:native lib
|
||||||
:lazy lazy
|
:lazy lazy
|
||||||
:native-lazy llib
|
:native-lazy llib
|
||||||
@@ -4003,7 +4008,7 @@
|
|||||||
~(defn ,alias ,;meta [,;formal-args]
|
~(defn ,alias ,;meta [,;formal-args]
|
||||||
(,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args))))
|
(,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args))))
|
||||||
|
|
||||||
(defmacro ffi/defbind
|
(defmacro ffi/defbind :flycheck
|
||||||
"Generate bindings for native functions in a convenient manner."
|
"Generate bindings for native functions in a convenient manner."
|
||||||
[name ret-type & body]
|
[name ret-type & body]
|
||||||
~(ffi/defbind-alias ,name ,name ,ret-type ,;body)))
|
~(ffi/defbind-alias ,name ,name ,ret-type ,;body)))
|
||||||
@@ -4014,6 +4019,47 @@
|
|||||||
###
|
###
|
||||||
###
|
###
|
||||||
|
|
||||||
|
(def- flycheck-specials @{})
|
||||||
|
|
||||||
|
(defn- flycheck-evaluator
|
||||||
|
``
|
||||||
|
An evaluator function that is passed to `run-context` that lints (flychecks) code for `flycheck`.
|
||||||
|
This means code will parsed and compiled, macros expanded, but the code will not be evaluated.
|
||||||
|
``
|
||||||
|
[thunk source env where]
|
||||||
|
(when (and (tuple? source) (= (tuple/type source) :parens))
|
||||||
|
(def head (source 0))
|
||||||
|
(def entry (get env head {}))
|
||||||
|
(def fc (get flycheck-specials head (get entry :flycheck)))
|
||||||
|
(cond
|
||||||
|
# Sometimes safe form
|
||||||
|
(function? fc)
|
||||||
|
(fc thunk source env where)
|
||||||
|
# Always safe form
|
||||||
|
fc
|
||||||
|
(thunk))))
|
||||||
|
|
||||||
|
(defn flycheck
|
||||||
|
```
|
||||||
|
Check a file for errors without running the file. Found errors will be printed to stderr
|
||||||
|
in the usual format. Top level functions and macros that have the metadata `:flycheck` will
|
||||||
|
also be evaluated during flychecking. For full control, The `flycheck` metadata can also be a function
|
||||||
|
the takes 4 arguments - `thunk`, `source`, `env`, and `where`, the same as the `:evaluator` argumnet to `run-context`.
|
||||||
|
Other arguments to `flycheck` are the same as `dofile`. Returns nil.
|
||||||
|
```
|
||||||
|
[path &keys kwargs]
|
||||||
|
(def mc @{})
|
||||||
|
(def new-env (make-env (get kwargs :env)))
|
||||||
|
(put new-env *flychecking* true)
|
||||||
|
(put new-env *module-cache* @{})
|
||||||
|
(put new-env *module-loading* @{})
|
||||||
|
(put new-env *module-make-env* (fn :make-flycheck-env [&] (make-env new-env)))
|
||||||
|
(try
|
||||||
|
(dofile path :evaluator flycheck-evaluator ;(kvs kwargs) :env new-env)
|
||||||
|
([e f]
|
||||||
|
(debug/stacktrace f e "")))
|
||||||
|
nil)
|
||||||
|
|
||||||
(defn- no-side-effects
|
(defn- no-side-effects
|
||||||
`Check if form may have side effects. If returns true, then the src
|
`Check if form may have side effects. If returns true, then the src
|
||||||
must not have side effects, such as calling a C function.`
|
must not have side effects, such as calling a C function.`
|
||||||
@@ -4029,59 +4075,29 @@
|
|||||||
(all no-side-effects (values src)))
|
(all no-side-effects (values src)))
|
||||||
true))
|
true))
|
||||||
|
|
||||||
(defn- is-safe-def [x] (no-side-effects (last x)))
|
(defn- is-safe-def [thunk source env where]
|
||||||
|
(if (no-side-effects (last source))
|
||||||
|
(thunk)))
|
||||||
|
|
||||||
(def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true
|
(defn- flycheck-importer
|
||||||
'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- is-safe-def
|
|
||||||
'defglobal is-safe-def 'varglobal is-safe-def 'defdyn true})
|
|
||||||
|
|
||||||
(def- importers {'import true 'import* true 'dofile true 'require true})
|
|
||||||
(defn- use-2 [evaluator args]
|
|
||||||
(each a args (import* (string a) :prefix "" :evaluator evaluator)))
|
|
||||||
|
|
||||||
(defn- flycheck-evaluator
|
|
||||||
``An evaluator function that is passed to `run-context` that lints (flychecks) code.
|
|
||||||
This means code will parsed and compiled, macros executed, but the code will not be run.
|
|
||||||
Used by `flycheck`.``
|
|
||||||
[thunk source env where]
|
[thunk source env where]
|
||||||
(when (tuple? source)
|
(let [[l c] (tuple/sourcemap source)
|
||||||
(def head (source 0))
|
newtup (tuple/setmap (tuple ;source :evaluator flycheck-evaluator) l c)]
|
||||||
(def safe-check
|
((compile newtup env where))))
|
||||||
(or
|
|
||||||
(safe-forms head)
|
|
||||||
(if (symbol? head)
|
|
||||||
(if (string/has-prefix? "define-" head) is-safe-def))))
|
|
||||||
(cond
|
|
||||||
# Sometimes safe form
|
|
||||||
(function? safe-check)
|
|
||||||
(if (safe-check source) (thunk))
|
|
||||||
# Always safe form
|
|
||||||
safe-check
|
|
||||||
(thunk)
|
|
||||||
# Use
|
|
||||||
(= 'use head)
|
|
||||||
(use-2 flycheck-evaluator (tuple/slice source 1))
|
|
||||||
# Import-like form
|
|
||||||
(importers head)
|
|
||||||
(let [[l c] (tuple/sourcemap source)
|
|
||||||
newtup (tuple/setmap (tuple ;source :evaluator flycheck-evaluator) l c)]
|
|
||||||
((compile newtup env where))))))
|
|
||||||
|
|
||||||
(defn flycheck
|
(defn- flycheck-use
|
||||||
``Check a file for errors without running the file. Found errors will be printed to stderr
|
[thunk source env where]
|
||||||
in the usual format. Macros will still be executed, however, so
|
(each a (drop 1 source) (import* (string a) :prefix "" :evaluator flycheck-evaluator)))
|
||||||
arbitrary execution is possible. Other arguments are the same as `dofile`. `path` can also be
|
|
||||||
a file value such as stdin. Returns nil.``
|
# Add metadata to defs and import macros for flychecking
|
||||||
[path &keys kwargs]
|
(each sym ['def 'var]
|
||||||
(def old-modcache (table/clone module/cache))
|
(put flycheck-specials sym is-safe-def))
|
||||||
(table/clear module/cache)
|
(each sym ['def- 'var- 'defglobal 'varglobal]
|
||||||
(try
|
(put (dyn sym) :flycheck is-safe-def))
|
||||||
(dofile path :evaluator flycheck-evaluator ;(kvs kwargs))
|
(each sym ['import 'import* 'dofile 'require]
|
||||||
([e f]
|
(put (dyn sym) :flycheck flycheck-importer))
|
||||||
(debug/stacktrace f e "")))
|
(each sym ['use]
|
||||||
(table/clear module/cache)
|
(put (dyn sym) :flycheck flycheck-use))
|
||||||
(merge-into module/cache old-modcache)
|
|
||||||
nil)
|
|
||||||
|
|
||||||
###
|
###
|
||||||
###
|
###
|
||||||
@@ -4310,7 +4326,7 @@
|
|||||||
(def infofile-src1 (string path s "bundle" s "info.jdn"))
|
(def infofile-src1 (string path s "bundle" s "info.jdn"))
|
||||||
(def infofile-src2 (string path s "info.jdn"))
|
(def infofile-src2 (string path s "info.jdn"))
|
||||||
(def infofile-src (cond (fexists infofile-src1) infofile-src1
|
(def infofile-src (cond (fexists infofile-src1) infofile-src1
|
||||||
(fexists infofile-src2) infofile-src2))
|
(fexists infofile-src2) infofile-src2))
|
||||||
(def info (-?> infofile-src slurp parse))
|
(def info (-?> infofile-src slurp parse))
|
||||||
(def bundle-name (get config :name (get info :name)))
|
(def bundle-name (get config :name (get info :name)))
|
||||||
(assertf bundle-name "unable to infer bundle name for %v, use :name argument" path)
|
(assertf bundle-name "unable to infer bundle name for %v, use :name argument" path)
|
||||||
@@ -4340,7 +4356,7 @@
|
|||||||
(when (os/stat infofile-dest :mode)
|
(when (os/stat infofile-dest :mode)
|
||||||
(def info (-> infofile-dest slurp parse))
|
(def info (-> infofile-dest slurp parse))
|
||||||
(def deps (seq [d :in (get info :dependencies @[])]
|
(def deps (seq [d :in (get info :dependencies @[])]
|
||||||
(string (if (dictionary? d) (get d :name) d))))
|
(string (if (dictionary? d) (get d :name) d))))
|
||||||
(def missing (filter (complement bundle/installed?) deps))
|
(def missing (filter (complement bundle/installed?) deps))
|
||||||
(when (next missing)
|
(when (next missing)
|
||||||
(error (string "missing dependencies " (string/join missing ", "))))
|
(error (string "missing dependencies " (string/join missing ", "))))
|
||||||
|
Reference in New Issue
Block a user