123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- (** This module check for duplicated tests in the source.contents
- This in intended to identify the copy/paste errors, where one location
- check for the same arguments twice or more.
- *)
- open StdLabels
- let identifier = "duplicate_test"
- let description = "Check for duplicate tests"
- let is_global = false
- let active = ref true
- type context = unit
- let initialize = Fun.id
- let finalize () = []
- module Expression = Tree.Expression
- (** Build a Hashtbl over the expression, ignoring the location in the
- expression *)
- module Table = Hashtbl.Make (struct
- type t = Expression.t'
- let equal : t -> t -> bool = Tree.Expression.eq (fun _ _ -> true)
- let hash : t -> int = Tree.Expression.hash (fun _ -> 0)
- end)
- module Instruction = struct
- type state = {
- predicates : (Expression.t' * S.pos) list;
- duplicates : (Expression.t' * S.pos list) list;
- }
- (** Keep the list of all the predicates and their position in a block, and
- the list of all the identified duplicated values. *)
- type t = state
- type t' = state
- let v : t -> t' = fun t -> t
- let default = { predicates = []; duplicates = [] }
- (** Label for a loop *)
- let location : S.pos -> string -> t = fun _ _ -> default
- (** Comment *)
- let comment : S.pos -> t = fun _ -> default
- (** Raw expression *)
- let expression : Expression.t' -> t = fun _ -> default
- let check_duplicates :
- (Expression.t' * S.pos) list -> (Expression.t' * S.pos list) list =
- fun predicates ->
- let table = Table.create 5 in
- let () = List.to_seq predicates |> Table.add_seq table in
- Table.to_seq_keys table
- |> Seq.group (Tree.Expression.eq (fun _ _ -> true))
- |> Seq.filter_map (fun keys ->
- (* Only take the first element for each group, we don’t need to
- repeat the key *)
- match Seq.uncons keys with
- | None -> None
- | Some (hd, _) -> (
- match Table.find_all table hd with
- | [] | _ :: [] -> None
- | other -> Some (hd, other)))
- |> List.of_seq
- (** Evaluate a clause.
- This function does two things :
- - report all errors from the bottom to top
- - add the clause in the actual level *)
- let predicate_of_clause : ?pos:S.pos -> t -> (Expression.t', t) S.clause -> t
- =
- fun ?pos t (pos2, predicate, blocks) ->
- let pos = Option.value ~default:pos2 pos in
- (* Remove the clauses using the function rnd because they repeating the
- same clause can generate a different result *)
- let should_discard =
- Tree.Expression.exists predicate ~f:(function
- | Tree.Ast.Function (_, T.Rand, _) | Tree.Ast.Function (_, T.Rnd, _) ->
- true
- | _ -> false)
- in
- {
- predicates =
- (match should_discard with
- | false -> (predicate, pos) :: t.predicates
- | true -> t.predicates);
- duplicates =
- List.fold_left blocks ~init:t.duplicates ~f:(fun acc t ->
- List.rev_append t.duplicates acc);
- }
- let if_ :
- S.pos ->
- (Expression.t', t) S.clause ->
- elifs:(Expression.t', t) S.clause list ->
- else_:(S.pos * t list) option ->
- t =
- fun pos clause ~elifs ~else_ ->
- ignore else_;
- (* Collect all the if clauses from this block, wait for the parent block to
- check each case for duplicates. *)
- let init = predicate_of_clause ~pos default clause in
- let state = List.fold_left elifs ~init ~f:predicate_of_clause in
- {
- state with
- duplicates = check_duplicates state.predicates @ state.duplicates;
- }
- let act : S.pos -> label:Expression.t' -> t list -> t =
- fun _pos ~label expressions ->
- ignore label;
- (* Collect all the elements reported from bottom to up. *)
- List.fold_left ~init:default expressions ~f:(fun state ex ->
- {
- predicates = [];
- duplicates = List.rev_append ex.duplicates state.duplicates;
- })
- let assign :
- S.pos ->
- (S.pos, Expression.t') S.variable ->
- T.assignation_operator ->
- Expression.t' ->
- t =
- fun _ _ _ _ -> default
- let call : S.pos -> T.keywords -> Expression.t' list -> t =
- fun _ _ _ -> default
- let for_ :
- S.pos ->
- (S.pos, Expression.t') S.variable ->
- start:Expression.t' ->
- to_:Expression.t' ->
- step:Expression.t' option ->
- t list ->
- t =
- fun _loc variable ~start ~to_ ~step statements ->
- ignore variable;
- ignore start;
- ignore to_;
- ignore step;
- List.fold_left ~init:default statements ~f:(fun state ex ->
- {
- predicates = [];
- duplicates = List.rev_append ex.duplicates state.duplicates;
- })
- end
- module Location = struct
- type t = (Expression.t' * S.pos list) list
- type context = unit
- (** No context *)
- (** Check if the given expression is involving the variable ARGS or $ARGS *)
- let is_args : Expression.t' -> bool = function
- | Tree.Ast.Ident { name; _ } ->
- String.equal name "ARGS" || String.equal name "$ARGS"
- | _ -> false
- let location : context -> S.pos -> Instruction.t' list -> t =
- fun () _ block ->
- (* Filter the tests from the top level and only keep them testing ARGS *)
- let duplicates =
- List.map block ~f:(fun t ->
- List.filter_map t.Instruction.predicates ~f:(fun v ->
- match (Tree.Expression.exists ~f:is_args) (fst v) with
- | true -> Some v
- | false -> None))
- |> List.concat |> Instruction.check_duplicates
- in
- List.fold_left ~init:duplicates block ~f:(fun state ex ->
- List.rev_append ex.Instruction.duplicates state)
- (** Create the report message *)
- let v' : Expression.t' * S.pos list -> Report.t option =
- fun (expr, pos) ->
- ignore expr;
- match (List.sort ~cmp:Report.compare_pos) pos with
- | [] -> None
- | _ :: [] -> None
- | hd :: tl ->
- let message =
- Format.asprintf "This case is duplicated line(s) %a"
- (Format.pp_print_list
- ~pp_sep:(fun f () -> Format.pp_print_char f ',')
- Report.pp_line)
- tl
- in
- (* Report all the messages as error. They do not break the game, but
- there is no question if it should *)
- Some (Report.error hd message)
- let v : t -> Report.t list =
- fun t -> List.filter_map t ~f:v' |> List.sort_uniq ~cmp:Report.compare
- end
|