package react-rules-of-hooks-ppx

  1. Overview
  2. Docs
This ppx validates the rules of React hooks in reason-react components

Install

dune-project
 Dependency

Authors

Maintainers

Sources

react-rules-of-hooks-ppx-1.4.0.tbz
sha256=75f5e3d4926ee92c9b95765e29208f1ae81c7130871a26914867d21d6829d719
sha512=13bfdf55f90e7201657188b84dcedafb1cc4b8f2f2a173fdb53cd98ddf427e43bb9e94aa105cd20576ebbd31fc680dd367835d78d06941e84a607793b2bc5086

doc/src/react_rules_of_hooks_ppx/hook.ml.html

Source file hook.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
open Ppxlib

let is_hook_name name =
  let len = String.length name in
  if len = 3 then name = "use"
  else
    len >= 4
    && name.[0] = 'u'
    && name.[1] = 's'
    && name.[2] = 'e'
    && ((name.[3] >= 'A' && name.[3] <= 'Z')
       || name.[3] = '_'
       || name.[3] = '\''
       || (name.[3] >= '0' && name.[3] <= '9'))

let last_component (longident : Longident.t) =
  match longident with Lident l -> Some l | Ldot (_, l) -> Some l | _ -> None

let is_hook_ident longident =
  match last_component longident with
  | Some name -> is_hook_name name
  | None -> false

let has_attribute name (attrs : attributes) =
  attrs |> List.exists (fun { attr_name; _ } -> attr_name.txt = name)

let is_jsx = has_attribute "JSX"

let call_ident (expr : Parsetree.expression) : Longident.t option =
  match expr.pexp_desc with
  | Pexp_apply ({ pexp_desc = Pexp_ident { txt; _ }; _ }, _)
    when not (is_jsx expr.pexp_attributes) ->
      Some txt
  | _ -> None

let hook_call (expr : Parsetree.expression) : Longident.t option =
  match call_ident expr with
  | Some lident when is_hook_ident lident -> Some lident
  | _ -> None

exception Hook_found

let hook_scanner =
  object
    inherit Ast_traverse.iter as super

    method! expression e =
      match hook_call e with
      | Some _ -> raise Hook_found
      | None -> super#expression e
  end

let contains_hook_call scan =
  try
    scan hook_scanner;
    false
  with Hook_found -> true

let expression_has_hook_calls expr =
  contains_hook_call (fun scanner -> scanner#expression expr)

let structure_has_hook_calls structure =
  contains_hook_call (fun scanner -> scanner#structure structure)

module With_deps = struct
  type t = { prefix : string; base : string; variant : int option }

  let bases =
    [
      "useEffect";
      "useLayoutEffect";
      "useInsertionEffect";
      "useCallback";
      "useMemo";
    ]

  let prefixes = [ "React."; "" ]

  let decode (name : string) : t option =
    let try_parse prefix base =
      let full_base = prefix ^ base in
      if String.starts_with ~prefix:full_base name then
        let suffix =
          String.sub name (String.length full_base)
            (String.length name - String.length full_base)
        in
        match suffix with
        | "" -> Some { prefix; base; variant = None }
        | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" ->
            Some { prefix; base; variant = Some (int_of_string suffix) }
        | _ -> None
      else None
    in
    List.find_map
      (fun base -> List.find_map (fun prefix -> try_parse prefix base) prefixes)
      bases

  let with_variant t variant = t.prefix ^ t.base ^ string_of_int variant

  type call = {
    name : string;
    info : t;
    fn_loc : Location.t;
    callback_arg : (arg_label * Parsetree.expression) option;
    deps_arg : (arg_label * Parsetree.expression) option;
  }

  let call (expr : Parsetree.expression) : call option =
    match expr.pexp_desc with
    | Pexp_apply
        ({ pexp_desc = Pexp_ident { txt; _ }; pexp_loc = fn_loc; _ }, args) ->
        let name = Longident.name txt in
        decode name
        |> Option.map (fun info ->
            {
              name;
              info;
              fn_loc;
              callback_arg = List.nth_opt args 0;
              deps_arg = List.nth_opt args 1;
            })
    | _ -> None
end