Lint

is the linter's core: the rule registry, the AST walker, and the public entry points (lint, lint-with, lint-source, lint-source-with). Plugin authors interact with it via register-rule!. See WRITING-RULES.md for the rule-author guide.

fix-source

defn

(Fn [(Ref String a)] (Result String ParseErr))

                        (fix-source src)
                    

applies every registered fix to src and returns the rewritten source. Findings whose rule has no fix, and fixes that overlap one already applied, are left alone; the rewrite is repeated until it reaches a fixpoint or a fixed pass limit. Only the bytes of fixed forms change, so formatting and comments elsewhere are preserved. A parse error is returned as such, and clean input comes back byte-identical.

fix-source-with

defn

(Fn [(Ref (Fn [(Ref String StaticLifetime)] Bool) a), (Ref String b)] (Result String ParseErr))

                        (fix-source-with keep? src)
                    

like fix-source, but only applies fixes whose rule names satisfy keep?.

fixable?

defn

(Fn [(Ref String StaticLifetime)] Bool)

                        (fixable? name)
                    

true iff at least one fix is registered for the rule called name.

lint

defn

(Fn [(Ref (Array (Box Located)) a)] (Array Diagnostic))

                        (lint forms)
                    

applies every registered rule to every node in forms, recursing into compound forms. Returns one Diagnostic per finding.

lint-source

defn

(Fn [(Ref String a)] (Result (Array Diagnostic) ParseErr))

                        (lint-source src)
                    

parses src via Reader.parse and lints with every registered rule. Returns (Result (Array Diagnostic) ParseErr) so a parse failure surfaces distinctly from a clean parse with diagnostics.

lint-source-with

defn

(Fn [(Ref (Fn [(Ref String StaticLifetime)] Bool) a), (Ref String b)] (Result (Array Diagnostic) ParseErr))

                        (lint-source-with keep? src)
                    

like lint-source, but only runs rules whose names satisfy keep?.

lint-with

defn

(Fn [(Ref (Fn [(Ref String StaticLifetime)] Bool) a), (Ref (Array (Box Located)) b)] (Array Diagnostic))

                        (lint-with keep? forms)
                    

like lint, but only runs rules whose names satisfy keep?.

list-rules

defn

(Fn [] (Array Rule))

                        (list-rules)
                    

returns a copy of the currently registered rule registry. Useful for angler --list-rules and for plugins that want to inspect the active rule set.

load-rules-from-source!

defn

(Fn [(Ref String a)] (Result Int ParseErr))

                        (load-rules-from-source! src)
                    

parses src as a rules file and registers each rule found. Each top-level form must be an array literal [<pattern> "name" "message"]. Symbols starting with ? in the pattern are metavariables, just as in register-pattern-rule!. Non-conforming top-level forms are silently skipped. Returns the number of rules loaded, or a parse error.

lst-head?

defn

(Fn [(Ref Form a), (Ref String b)] Bool)

                        (lst-head? f name)
                    

true iff f is (Form.Lst …) whose first child is a Form.Sym matching name.

nth-form

defn

(Fn [(Ref (Array (Box Located)) a), Int] (Ref Form a))

                        (nth-form items i)
                    

pulls the Form out of the i-th Located child of an Array (Box Located). Most rules want the structural shape, not the position, when descending into a compound form's children.

nth-located

defn

(Fn [(Ref (Array (Box a)) b), Int] (Ref a b))

                        (nth-located items i)
                    

returns the i-th Located child of an Array (Box Located). Use it over nth-form when a rule or fix needs the child's source position as well as its shape.

register-fix!

defn

(Fn [String, (Fn [(Ref Located a), (Ref String b)] (Maybe Fix))] ())

                        (register-fix! name fn)
                    

registers a rewrite for the rule called name. fn receives a node the rule fired on and the full source it came from, and returns the Fix to apply, or Nothing if this particular node has no rewrite that is certainly semantics-preserving.

Several fixes may share a rule name; the first one to return Just wins. A rule without a fix is reported by lint exactly as before and skipped by fix-source.

register-pattern-fix-rule!

defn

(Fn [String, String, (Ref String a), String, String] ())

                        (register-pattern-fix-rule! name description pattern-str message template)
                    

is register-pattern-rule! plus a rewrite: template-str is source text in which every ?name is replaced by the original bytes the metavariable matched, and the matched node is replaced by the result.

(Lint.register-pattern-fix-rule! @"double-not"
                                 @"(not (not x)) is just x"
                                 "(not (not ?x))"
                                 @"(not (not x)) is just x"
                                 @"?x")

Only register a template that is unconditionally semantics-preserving: fix-source applies it without asking.

register-pattern-rule!

defn

(Fn [String, String, (Ref String a), String] ())

                        (register-pattern-rule! name description pattern-str message)
                    

registers a lint rule that fires when pattern-str (a single Carp form) structurally matches a node. Symbols starting with ? are metavariables: they match any form, and if the same ?name appears more than once the bound forms must have equal string representations.

(Lint.register-pattern-rule! @"set-self"
                             @"(set! x x) is a no-op"
                             "(set! ?x ?x)"
                             @"(set! x x) is a no-op")

register-rule!

defn

(Fn [String, String, (Fn [(Ref Located a)] (Maybe Diagnostic))] ())

                        (register-rule! name description fn)
                    

registers a rule under name with a one-line description. Subsequent runs of Lint.lint / Lint.lint-source include it. Built-ins call this at load time; plugins do the same after loading angler.

Caveat: Carp initializes global defs in dependency-graph order, not in textual or load order. If your plugin uses a top-level def to call register-rule!, its slot in the registry — and therefore its slot in the diagnostic output for any one form — is unspecified relative to angler's built-ins. Rule firing is still correct; only the order of diagnostics on a single node varies. If you need strict ordering, register from main instead of from a top-level def.

sym?

defn

(Fn [(Ref Form a), (Ref String b)] Bool)

                        (sym? f name)
                    

true iff f is a Form.Sym whose dotted name equals name. name may itself contain . to match a multi-segment symbol — e.g. (sym? f "Parser.try") matches Parser.try.