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. 24Doesn'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 module-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-name] 41 (or (-?> module getmetatable (. :arity-check?)) 42 (pcall debug.getlocal #nil 1) ; PUC 5.1 can't use debug.getlocal for this 43 ;; I don't love this method of configuration but it gets the job done. 44 (match (and module-name os os.getenv (os.getenv "FENNEL_LINT_MODULES")) 45 module-pattern (module-name:find module-pattern)))) 46 47(fn descend [target [part & parts]] 48 (if (= nil part) target 49 (= :table (type target)) (match (. target part) 50 new-target (descend new-target parts)) 51 target)) 52 53(fn min-arity [target last-required name] 54 (match (debug.getlocal target last-required) 55 localname (if (and (localname:match "^_3f") (< 0 last-required)) 56 (min-arity target (- last-required 1)) 57 last-required) 58 _ last-required)) 59 60(fn arity-check-call [[f & args] scope] 61 "Perform static arity checks on static function calls in a module." 62 (let [last-arg (. args (length args)) 63 arity (if (: (tostring f) :find ":") ; method 64 (+ (length args) 1) 65 (length args)) 66 [f-local & parts] (or (multi-sym? f) []) 67 module-name (-?> scope.symmeta (. (tostring f-local)) (. :required)) 68 module (and module-name (require module-name)) 69 field (table.concat parts ".") 70 target (descend module parts)] 71 (when (and (arity-check? module module-name) _G.debug _G.debug.getinfo 72 module (not (varg? last-arg)) (not (list? last-arg))) 73 (assert-compile (= (type target) :function) 74 (string.format "Missing function %s in module %s" 75 (or field :?) module-name) f) 76 (match (_G.debug.getinfo target) 77 {: nparams :what "Lua"} 78 (let [min (min-arity target nparams f)] 79 (assert-compile (<= min arity) 80 (: "Called %s with %s arguments, expected at least %s" 81 :format f arity min) f)))))) 82 83(fn check-unused [ast scope] 84 (each [symname (pairs scope.symmeta)] 85 (assert-compile (or (. scope.symmeta symname :used) (symname:find "^_")) 86 (string.format "unused local %s" (or symname :?)) ast))) 87 88{:destructure save-require-meta 89 :symbol-to-expression check-module-fields 90 :call arity-check-call 91 ;; Note that this will only check unused args inside functions and let blocks, 92 ;; not top-level locals of a chunk. 93 :fn check-unused 94 :do check-unused 95 :chunk check-unused 96 :name "fennel/linter" 97 :versions ["1.0.0"]} 98