dup_test.ml 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. (** This module check for duplicated tests in the source.contents
  2. This in intended to identify the copy/paste errors, where one location
  3. check for the same arguments twice or more.
  4. *)
  5. open StdLabels
  6. let identifier = "duplicate_test"
  7. let description = "Check for duplicate tests"
  8. let is_global = false
  9. let active = ref true
  10. type context = unit
  11. let initialize = Fun.id
  12. let finalize () = []
  13. module Expression = Tree.Expression
  14. (** Build a Hashtbl over the expression, ignoring the location in the
  15. expression *)
  16. module Table = Hashtbl.Make (struct
  17. type t = Expression.t'
  18. let equal : t -> t -> bool = Tree.Expression.eq (fun _ _ -> true)
  19. let hash : t -> int = Tree.Expression.hash (fun _ -> 0)
  20. end)
  21. module Instruction = struct
  22. type state = {
  23. predicates : (Expression.t' * S.pos) list;
  24. duplicates : (Expression.t' * S.pos list) list;
  25. }
  26. (** Keep the list of all the predicates and their position in a block, and
  27. the list of all the identified duplicated values. *)
  28. type t = state
  29. type t' = state
  30. let v : t -> t' = fun t -> t
  31. let default = { predicates = []; duplicates = [] }
  32. (** Label for a loop *)
  33. let location : S.pos -> string -> t = fun _ _ -> default
  34. (** Comment *)
  35. let comment : S.pos -> t = fun _ -> default
  36. (** Raw expression *)
  37. let expression : Expression.t' -> t = fun _ -> default
  38. let check_duplicates :
  39. (Expression.t' * S.pos) list -> (Expression.t' * S.pos list) list =
  40. fun predicates ->
  41. let table = Table.create 5 in
  42. let () = List.to_seq predicates |> Table.add_seq table in
  43. Table.to_seq_keys table
  44. |> Seq.group (Tree.Expression.eq (fun _ _ -> true))
  45. |> Seq.filter_map (fun keys ->
  46. (* Only take the first element for each group, we don’t need to
  47. repeat the key *)
  48. match Seq.uncons keys with
  49. | None -> None
  50. | Some (hd, _) -> (
  51. match Table.find_all table hd with
  52. | [] | _ :: [] -> None
  53. | other -> Some (hd, other)))
  54. |> List.of_seq
  55. (** Evaluate a clause.
  56. This function does two things :
  57. - report all errors from the bottom to top
  58. - add the clause in the actual level *)
  59. let predicate_of_clause : ?pos:S.pos -> t -> (Expression.t', t) S.clause -> t
  60. =
  61. fun ?pos t (pos2, predicate, blocks) ->
  62. let pos = Option.value ~default:pos2 pos in
  63. (* Remove the clauses using the function rnd because they repeating the
  64. same clause can generate a different result *)
  65. let should_discard =
  66. Tree.Expression.exists predicate ~f:(function
  67. | Tree.Ast.Function (_, T.Rand, _) | Tree.Ast.Function (_, T.Rnd, _) ->
  68. true
  69. | _ -> false)
  70. in
  71. {
  72. predicates =
  73. (match should_discard with
  74. | false -> (predicate, pos) :: t.predicates
  75. | true -> t.predicates);
  76. duplicates =
  77. List.fold_left blocks ~init:t.duplicates ~f:(fun acc t ->
  78. List.rev_append t.duplicates acc);
  79. }
  80. let if_ :
  81. S.pos ->
  82. (Expression.t', t) S.clause ->
  83. elifs:(Expression.t', t) S.clause list ->
  84. else_:(S.pos * t list) option ->
  85. t =
  86. fun pos clause ~elifs ~else_ ->
  87. ignore else_;
  88. (* Collect all the if clauses from this block, wait for the parent block to
  89. check each case for duplicates. *)
  90. let init = predicate_of_clause ~pos default clause in
  91. let state = List.fold_left elifs ~init ~f:predicate_of_clause in
  92. {
  93. state with
  94. duplicates = check_duplicates state.predicates @ state.duplicates;
  95. }
  96. let act : S.pos -> label:Expression.t' -> t list -> t =
  97. fun _pos ~label expressions ->
  98. ignore label;
  99. (* Collect all the elements reported from bottom to up. *)
  100. List.fold_left ~init:default expressions ~f:(fun state ex ->
  101. {
  102. predicates = [];
  103. duplicates = List.rev_append ex.duplicates state.duplicates;
  104. })
  105. let assign :
  106. S.pos ->
  107. (S.pos, Expression.t') S.variable ->
  108. T.assignation_operator ->
  109. Expression.t' ->
  110. t =
  111. fun _ _ _ _ -> default
  112. let call : S.pos -> T.keywords -> Expression.t' list -> t =
  113. fun _ _ _ -> default
  114. end
  115. module Location = struct
  116. type t = (Expression.t' * S.pos list) list
  117. type context = unit
  118. (** No context *)
  119. (** Check if the given expression is involving the variable ARGS or $ARGS *)
  120. let is_args : Expression.t' -> bool = function
  121. | Tree.Ast.Ident { name; _ } ->
  122. String.equal name "ARGS" || String.equal name "$ARGS"
  123. | _ -> false
  124. let location : context -> S.pos -> Instruction.t' list -> t =
  125. fun () _ block ->
  126. (* Filter the tests from the top level and only keep them testing ARGS *)
  127. let duplicates =
  128. List.map block ~f:(fun t ->
  129. List.filter_map t.Instruction.predicates ~f:(fun v ->
  130. match (Tree.Expression.exists ~f:is_args) (fst v) with
  131. | true -> Some v
  132. | false -> None))
  133. |> List.concat |> Instruction.check_duplicates
  134. in
  135. List.fold_left ~init:duplicates block ~f:(fun state ex ->
  136. List.rev_append ex.Instruction.duplicates state)
  137. (** Create the report message *)
  138. let v' : Expression.t' * S.pos list -> Report.t option =
  139. fun (expr, pos) ->
  140. ignore expr;
  141. match (List.sort ~cmp:Report.compare_pos) pos with
  142. | [] -> None
  143. | _ :: [] -> None
  144. | hd :: tl ->
  145. let message =
  146. Format.asprintf "This case is duplicated line(s) %a"
  147. (Format.pp_print_list
  148. ~pp_sep:(fun f () -> Format.pp_print_char f ',')
  149. Report.pp_line)
  150. tl
  151. in
  152. (* Report all the messages as error. They do not break the game, but
  153. there is no question if it should *)
  154. Some (Report.error hd message)
  155. let v : t -> Report.t list =
  156. fun t -> List.filter_map t ~f:v' |> List.sort_uniq ~cmp:Report.compare
  157. end