This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,189 @@
open Ppxlib
open Base
let error ~loc fmt = Location.raise_errorf ~loc (Stdlib.( ^^ ) "ppx_base_lint:" fmt)
type suspicious_id = Stdlib_submodule of string
let rec iter_suspicious (id : Longident.t) ~f =
match id with
| Ldot (Lident "Stdlib", s)
when String.( <> ) s ""
&&
match s.[0] with
| 'A' .. 'Z' -> true
| _ -> false -> f (Stdlib_submodule s)
| Ldot (x, _) -> iter_suspicious x ~f
| Lapply (a, b) ->
iter_suspicious a ~f;
iter_suspicious b ~f
| Lident _ -> ()
;;
let zero_modules () =
Stdlib.Sys.readdir "."
|> Array.to_list
|> List.filter ~f:(fun fn -> Stdlib.Filename.check_suffix fn "0.ml")
|> List.map ~f:(fun fn ->
String.capitalize (String.sub fn ~pos:0 ~len:(String.length fn - 4)))
|> Set.of_list (module String)
;;
let check_open (id : Longident.t Asttypes.loc) =
match id.txt with
| Lident "Stdlib" -> error ~loc:id.loc "you are not allowed to open Stdlib inside Base"
| _ -> ()
;;
let rec is_stdlib_dot_something : Longident.t -> bool = function
| Ldot (Lident "Stdlib", _) -> true
| Ldot (id, _) -> is_stdlib_dot_something id
| _ -> false
;;
let print_payload ppf = function
| PStr x -> Pprintast.structure ppf x
| PSig x -> Pprintast.signature ppf x
| PTyp x -> Pprintast.core_type ppf x
| PPat (x, None) -> Pprintast.pattern ppf x
| PPat (x, Some w) ->
Stdlib.Format.fprintf ppf "%a@ when@ %a" Pprintast.pattern x Pprintast.expression w
;;
let remove_loc =
object
inherit Ast_traverse.map
method! location _ = Location.none
method! location_stack _ = []
end
;;
let check current_module =
let zero_modules = zero_modules () in
object
inherit Ast_traverse.iter as super
method! longident_loc { txt = id; loc } =
(* Note: we don't distinguish between module identifiers and constructors names.
Since there is no [Stdlib.String], [Stdlib.Array], ... constructors this is not a
problem. *)
iter_suspicious id ~f:(fun (Stdlib_submodule m) ->
if not (Set.mem zero_modules m)
then (* We are allowed to use Stdlib modules that don't have a Foo0 version *)
()
else if String.equal (m ^ "0") current_module
then () (* Foo0 is allowed to use Stdlib.Foo *)
else (
match current_module with
| "Import0" | "Base" -> ()
| _ -> error ~loc "you cannot use [Stdlib.%s] here, use [%s0] instead" m m))
(* We allow references to Stdlib in types. This is primarily to allow ppx-derived code
to refer to Stdlib. *)
method! core_type _ = ()
method! expression e =
super#expression e;
match e.pexp_desc with
| Pexp_open ({ popen_expr = { pmod_desc = Pmod_ident id; _ }; _ }, _) ->
check_open id
| _ -> ()
method! open_description op =
super#open_description op;
check_open op.popen_expr
method! module_binding mb =
super#module_binding mb;
match current_module with
| "Import0" -> ()
| _ ->
(match mb.pmb_expr.pmod_desc with
| Pmod_ident { txt = id; _ } when is_stdlib_dot_something id ->
error
~loc:mb.pmb_loc
"you cannot alias [Stdlib] sub-modules, use them directly"
| _ -> ())
method! attributes attrs =
super#attributes attrs;
let is_cold attr = String.equal attr.attr_name.txt "cold" in
match List.find attrs ~f:is_cold with
| None -> ()
| Some attr ->
let expansion =
Ppx_cold.expand_cold_attribute attr
|> List.map ~f:(fun a ->
{ a with
attr_name =
{ a.attr_name with
txt =
String.chop_prefix a.attr_name.txt ~prefix:"ocaml."
|> Option.value ~default:a.attr_name.txt
}
})
in
let is_part_of_expansion attr =
List.exists expansion ~f:(fun a ->
String.equal a.attr_name.txt attr.attr_name.txt
|| String.equal ("ocaml." ^ a.attr_name.txt) attr.attr_name.txt)
in
let new_attrs =
List.concat_map attrs ~f:(fun a ->
if is_cold a
then a :: expansion
else if is_part_of_expansion a
then []
else [ a ])
in
if not
(Poly.equal (remove_loc#attributes attrs) (remove_loc#attributes new_attrs))
then (
(* Remove attributes written by the user that correspond to attributes in the
expansion *)
List.iter attrs ~f:(fun a ->
if is_part_of_expansion a
then Driver.register_correction ~loc:a.attr_loc ~repl:"");
let attribute_level =
String.make
(attr.attr_name.loc.loc_start.pos_cnum
- attr.attr_loc.loc_start.pos_cnum
- 1)
'@'
in
let repl =
Stdlib.Format.asprintf
"@[<h>%a@]"
(Stdlib.Format.pp_print_list (fun ppf x ->
Stdlib.Format.fprintf
ppf
"[%s%s@ %a]"
attribute_level
x.attr_name.txt
print_payload
x.attr_payload))
(attr :: expansion)
in
Driver.register_correction ~loc:attr.attr_loc ~repl)
end
;;
let module_of_loc (loc : Location.t) =
String.capitalize
(Stdlib.Filename.chop_extension (Stdlib.Filename.basename loc.loc_start.pos_fname))
;;
let () =
Ppxlib.Driver.register_transformation
"base_lint"
~impl:(function
| [] -> []
| { pstr_loc = loc; _ } :: _ as st ->
(check (module_of_loc loc))#structure st;
st)
~intf:(function
| [] -> []
| { psig_loc = loc; _ } :: _ as sg ->
(check (module_of_loc loc))#signature sg;
sg)
;;