git.m455.casa

fa

clone url: git://git.m455.casa/fa


esperbuild/espersrc/fennel-0.7.0/src/linter.fnl

1 ;; An example of some possible linters using Fennel's --plugin option.
2
3 ;; The first two linters here can only function on static module
4 ;; use. For instance, this code can be checked because they use static
5 ;; field access on a local directly bound to a require call:
6
7 ;; (local m (require :mymodule))
8 ;; (print m.field) ; fails if mymodule lacks a :field field
9 ;; (print (m.function 1 2 3)) ; fails unless mymodule.function takes 3 args
10
11 ;; However, these cannot:
12
13 ;; (local m (do (require :mymodule)) ; m is not directly bound
14 ;; (print (. m field)) ; not a static field reference
15 ;; (let [f m.function]
16 ;; (print (f 1 2 3)) ; intermediate local, not a static field call on m
17
18 ;; Still, pretty neat, huh?
19
20 ;; This file is provided as an example and is not part of Fennel's public API.
21
22 (fn save-require-meta [from to scope]
23 "When destructuring, save module name if local is bound to a `require' call.
24 Doesn't do any linting on its own; just saves the data for other linters."
25 (when (and (sym? to) (not (multi-sym? to)) (list? from)
26 (sym? (. from 1)) (= :require (tostring (. from 1)))
27 (= :string (type (. from 2))))
28 (let [meta (. scope.symmeta (tostring to))]
29 (set meta.required (tostring (. from 2))))))
30
31 (fn check-module-fields [symbol scope]
32 "When referring to a field in a local that's a module, make sure it exists."
33 (let [[module-local field] (or (multi-sym? symbol) [])
34 module-name (-?> scope.symmeta (. (tostring f-local)) (. :required))
35 module (and module-name (require module-name))]
36 (assert-compile (or (= module nil) (not= (. module field) nil))
37 (string.format "Missing field %s in module %s"
38 (or field :?) (or module-name :?)) symbol)))
39
40 (fn arity-check? [module] (-?> module getmetatable (. :arity-check?)))
41
42 (fn arity-check-call [[f & args] scope]
43 "Perform static arity checks on static function calls in a module."
44 (let [arity (# args)
45 last-arg (. args arity)
46 [f-local field] (or (multi-sym? f) [])
47 module-name (-?> scope.symmeta (. (tostring f-local)) (. :required))
48 module (and module-name (require module-name))]
49 (when (and (arity-check? module) debug debug.getinfo
50 (not (varg? last-arg)) (not (list? last-arg)))
51 (assert-compile (= (type (. module field)) :function)
52 (string.format "Missing function %s in module %s"
53 (or field :?) module-name) f)
54 (match (debug.getinfo (. module field))
55 {: nparams :what "Lua" :isvararg true}
56 (assert-compile (<= nparams (# args))
57 (: "Called %s.%s with %s arguments, expected %s+"
58 :format f-local field arity nparams) f)
59 {: nparams :what "Lua" :isvararg false}
60 (assert-compile (= nparams (# args))
61 (: "Called %s.%s with %s arguments, expected %s"
62 :format f-local field arity nparams) f)))))
63
64 (fn check-unused [ast scope]
65 (each [symname (pairs scope.symmeta)]
66 (assert-compile (or (. scope.symmeta symname :used) (symname:find "^_"))
67 (string.format "unused local %s" (or symname :?)) ast)))
68
69 {:destructure save-require-meta
70 :symbol-to-expression check-module-fields
71 :call arity-check-call
72 ;; Note that this will only check unused args inside functions and let blocks,
73 ;; not top-level locals of a chunk.
74 :fn check-unused
75 :do check-unused}