Closed emillon closed 8 years ago
I would encourage you to consider a declarative model where, instead of having a main program and plugins that perform imperative mutations to rule bases, you provide a functional API to build rulesets, plus a function to "become a linter" from a given ruleset. This requires good API design, but if done well this can be almost as convenient as what you propose -- in terms of amount of code for end-users to write -- with a naturally more robust/predictable semantics.
You can easily build a more imperative interface on top of a functional API: add a global reference to the "current ruleset" and define ignore/error/...
that mutate it. On the other hand, carving a functional API out of an imperative plugin interface after the fact is very hard.
Do you mean something like xmonad's config system? That would look like the following:
let () =
let config = { default_config with rate_expr = default_config.rate_expr <|> custom_rate_expr } in
let mapper = Ppx_lint.lint_mapper config in
register "custom_lint" mapper
It seems to be a good idea but API design will be tricky indeed.
Yes, the Xmonad's configuration system works like this.
Some domain problem types may be the following:
rule
, a single rule (this could be an algebraic type as either a rule on expresions or...; or a record of an expression rule, a module rule, etc., to allow to build combined rules) including rule documentationruleset
, a set of rules (add : rule -> ruleset -> rule
, ignore : rule -> ruleset -> rule
, etc.); maybe there are different levels of "activation state" (warning or errors?) on rules that must be taken into account when modifying rulesets?config
, that is either a ruleset plus some cross-rules concerns (format of output, plugin-specific command-line options to add to the linter, whatever), or just the cross-rules concernsThen a standard ocamllint.ml
file could be something like
module R = Lint_ruleset
module Std = Lint_standard_rules
let rules =
Std.default_rules
|> R.union Lint_cryptosense.default_rules
|> R.add Std.(partial_function "List.hd")
|> R.remove Std.comparison_to_boolean
let () = Ocamllint_run.lint Ocamllint_config.default rules
Then ocamlbuild -use-ocamlfind -package lint -package lint-cryptosense ocamllint.native -- .
would compile the linter and run it with the current directory (.
) as only command-line argument.
A linting tool should have sensible defaults and configurable rules, since it's meant to enforce rules, not prescribe them.
There are a couple problems that need to be solved:
I think that a simple way to do that is to expose an API and add a mechanism to load a module with the deriver. This is similar to how
ppx_deriving
modules are written. One would create aOcamllint_config
module with the following contents and pass-package ocamllint.ppx_lint -ppxopt ocamllint.ppx_lint,ocamllint_config.cma
: