package ppx_enforce
Install
dune-project
Dependency
Authors
Maintainers
Sources
md5=092a959c08fd1275987c2cc001b9c693
sha512=6f1d5c1e522160d35bb5fc61b0f21bf0e41e1b7a32602fe3aee47f9f878b2d9f8e01307d19c03b8e7da410ab073bbd0ac87a1a744eda56e04046e9e74fcd4888
Description
A configurable PPX rewriter that raises compilation errors when required function calls are absent from a source file. The mirror of ppx_forbid. Useful for enforcing coding standards like 'every widget must self-register'. Supports per-project configuration via .ppx_enforce files and [@@@enforce_exempt] attributes for suppression.
README
ppx_forbid
A configurable OCaml PPX that raises compile-time errors when forbidden functions or modules are used. Enforce coding standards automatically -- no more "please don't use X" in code review.
Use cases
- Eio migration: ban blocking
Unix.open_process_in,Unix.sleep,Thread.create - Safety: ban
Obj.magicand other unsafe operations - TUI apps: ban
print_endline/Printf.printf(they corrupt the terminal) - Theming: ban hardcoded color functions, enforce themed helpers
- API migration: ban deprecated functions with actionable suggestions
Quick start
opam install ppx_forbidAdd to your dune file:
(library
(name mylib)
(preprocess (pps ppx_forbid)))Create a .ppx_forbid config in your project:
module Obj "Obj is unsafe and breaks type safety"
function Unix.sleep "Use Eio.Time.sleep"
function Thread.create "Use Eio.Fiber.fork"That's it. Any use of Obj, Unix.sleep, or Thread.create will now fail to compile:
File "src/myfile.ml", line 42, characters 10-30:
42 | let _ = Unix.sleep 5 in
^^^^^^^^^^
Error: Forbidden call: Unix.sleep is not allowed.
Suggestion: Use Eio.Time.sleep
Use [@allow_forbidden "reason"] to suppress.Unqualified Stdlib functions are also caught -- prerr_endline matches a function Stdlib.prerr_endline rule.
Config file format
# Comments start with #
# Forbid an entire module
module <ModuleName> "<reason>"
# Forbid a specific function
function <Module.function_name> "<suggestion>"
# Include another config file (paths relative to this file)
include ../base.ppx_forbidPer-directory configs
The PPX searches for .ppx_forbid starting from the source file's directory and walking up to the project root. This lets you have stricter rules for specific subdirectories:
project/
.ppx_forbid # project-wide rules
src/
ui/
.ppx_forbid # UI-specific rules (can `include ../../.ppx_forbid`)You can also pass an explicit config path:
(preprocess (pps (ppx_forbid --config .ppx_forbid.strict)))Suppression
When you genuinely need a forbidden function, annotate with [@allow_forbidden "reason"]:
(* On an expression *)
let raw = (Obj.magic ptr : bytes) [@allow_forbidden "FFI boundary"]
(* On a binding *)
let[@allow_forbidden "logger writes to stderr by design"] log msg =
prerr_endline msgThe reason string is required and documents why the exception is acceptable.
Enforcing required calls
This repository also ships ppx_enforce, the mirror image of ppx_forbid. It raises a compile-time error when a source file does not contain a required function call.
Install it with:
opam install ppx_enforceAdd it to your dune file:
(library
(name my_widgets)
(preprocess (pps ppx_enforce))
(preprocessor_deps .ppx_enforce))Create a .ppx_enforce config:
call Miaou_registry.register "Widget modules must self-register"Each processed file must now contain a matching call such as:
let () = Miaou_registry.register ~name:"my-widget" ~mli:"..." ()If a file is intentionally outside the rule, add a file-level exemption:
[@@@enforce_exempt]or exempt one requirement:
[@@@enforce_exempt "Miaou_registry.register"]Default rules
When no .ppx_forbid file is found, a single default rule applies:
Item | Reason |
|---|---|
| Obj is unsafe and breaks type safety |
Real-world example
Project-wide config (.ppx_forbid):
module Obj "Obj is unsafe and breaks type safety"
function Unix.open_process_in "Use Eio.Process.run or Common.run_out"
function Unix.open_process_out "Use Eio.Process.run"
function Unix.system "Use Eio.Process.run or Common.run"
function Unix.sleep "Use Eio.Time.sleep"
function Thread.create "Use Eio.Fiber.fork or Eio.Fiber.fork_daemon"TUI-specific config (src/ui/.ppx_forbid):
include ../../.ppx_forbid
function Stdlib.print_endline "Use logging or TUI display functions"
function Stdlib.prerr_endline "Use logging or Toast notifications"
function Printf.printf "Use logging or TUI display functions"Requirements
- OCaml >= 4.14
- ppxlib >= 0.28.0
- dune >= 3.13