diff options
| author | 2025-09-20 10:30:28 -0500 | |
|---|---|---|
| committer | 2025-09-20 10:32:16 -0500 | |
| commit | 1ff26d702a665d7c66774e635b685345e46a8064 (patch) | |
| tree | 7cb497fb69893446128c7740d446e91f9bf03d1a /src | |
| parent | Manually declare chroot on macos. (diff) | |
Refactor flycheck to allow customization. Address #1638
Bindings can define their own flycheckers in a simple fashion.
Diffstat (limited to 'src')
| -rw-r--r-- | src/boot/boot.janet | 140 |
1 files changed, 78 insertions, 62 deletions
diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 61d70871..e4e74d01 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -7,7 +7,7 @@ ### ### -(def defn :macro +(def defn :macro :flycheck ``` (defn name & more) @@ -43,7 +43,7 @@ # Build return value ~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start))))) -(defn defmacro :macro +(defn defmacro :macro :flycheck "Define a macro." [name & more] (setdyn name @{}) # override old macro definitions in the case of a recursive macro @@ -57,12 +57,12 @@ [f & args] (f ;args)) -(defmacro defmacro- +(defmacro defmacro- :flycheck "Define a private macro that will not be exported." [name & more] (apply defn name :macro :private more)) -(defmacro defn- +(defmacro defn- :flycheck "Define a private function that will not be exported." [name & 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 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." [x &opt err] (def v (gensym)) @@ -154,7 +154,7 @@ ,v (,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 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 @@ -171,6 +171,9 @@ (defdyn *macro-form* "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* "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) -(defmacro varfn +(defmacro varfn :flycheck ``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` already exists in the environment, it is rebound to the new function. Returns @@ -3945,7 +3948,7 @@ [& forms] (def state (gensym)) (def loaded (gensym)) - ~((fn [] + ~((fn :delay [] (var ,state nil) (var ,loaded nil) (fn [] @@ -3977,7 +3980,7 @@ :lazy lazy :map-symbols map-symbols})) - (defmacro ffi/defbind-alias + (defmacro ffi/defbind-alias :flycheck "Generate bindings for native functions in a convenient manner. Similar to defbind but allows for the janet function name to be different than the FFI function." @@ -3988,6 +3991,8 @@ (def formal-args (map 0 arg-pairs)) (def type-args (map 1 arg-pairs)) (def computed-type-args (eval ~[,;type-args])) + (if (dyn *flychecking*) + (break ~(defn ,alias ,;meta [,;formal-args] nil))) (def {:native lib :lazy lazy :native-lazy llib @@ -4003,7 +4008,7 @@ ~(defn ,alias ,;meta [,;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." [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 `Check if form may have side effects. If returns true, then the src must not have side effects, such as calling a C function.` @@ -4029,59 +4075,29 @@ (all no-side-effects (values src))) true)) -(defn- is-safe-def [x] (no-side-effects (last x))) - -(def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true - '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- is-safe-def [thunk source env where] + (if (no-side-effects (last source)) + (thunk))) -(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`.`` +(defn- flycheck-importer [thunk source env where] - (when (tuple? source) - (def head (source 0)) - (def safe-check - (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)))))) + (let [[l c] (tuple/sourcemap source) + newtup (tuple/setmap (tuple ;source :evaluator flycheck-evaluator) l c)] + ((compile newtup env where)))) -(defn flycheck - ``Check a file for errors without running the file. Found errors will be printed to stderr - in the usual format. Macros will still be executed, however, so - arbitrary execution is possible. Other arguments are the same as `dofile`. `path` can also be - a file value such as stdin. Returns nil.`` - [path &keys kwargs] - (def old-modcache (table/clone module/cache)) - (table/clear module/cache) - (try - (dofile path :evaluator flycheck-evaluator ;(kvs kwargs)) - ([e f] - (debug/stacktrace f e ""))) - (table/clear module/cache) - (merge-into module/cache old-modcache) - nil) +(defn- flycheck-use + [thunk source env where] + (each a (drop 1 source) (import* (string a) :prefix "" :evaluator flycheck-evaluator))) + +# Add metadata to defs and import macros for flychecking +(each sym ['def 'var] + (put flycheck-specials sym is-safe-def)) +(each sym ['def- 'var- 'defglobal 'varglobal] + (put (dyn sym) :flycheck is-safe-def)) +(each sym ['import 'import* 'dofile 'require] + (put (dyn sym) :flycheck flycheck-importer)) +(each sym ['use] + (put (dyn sym) :flycheck flycheck-use)) ### ### @@ -4310,7 +4326,7 @@ (def infofile-src1 (string path s "bundle" s "info.jdn")) (def infofile-src2 (string path s "info.jdn")) (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 bundle-name (get config :name (get info :name))) (assertf bundle-name "unable to infer bundle name for %v, use :name argument" path) @@ -4340,7 +4356,7 @@ (when (os/stat infofile-dest :mode) (def info (-> infofile-dest slurp parse)) (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)) (when (next missing) (error (string "missing dependencies " (string/join missing ", ")))) |
