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
(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
(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?
(Fn [(Ref String StaticLifetime)] Bool)
(fixable? name)
true iff at least one fix is registered for the rule
called name.
lint
(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
(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
(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
(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
(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!
(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?
(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
(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
(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!
(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!
(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!
(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!
(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?
(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.