123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- open StdLabels
- module Report = Qsp_syntax.Report
- (** Filter the results given by the analysis *)
- let filter_report : Args.filters -> Report.t list -> Report.t -> Report.t list =
- fun filters reports r ->
- let is_ok =
- match filters.level with
- | None -> true
- | Some level -> Report.level_to_enum level >= Report.level_to_enum r.level
- in
- match is_ok with true -> r :: reports | _ -> reports
- type ctx = { error_nb : int; warn_nb : int; debug_nb : int; fatal_error : bool }
- (*
- List all the controls to apply
- *)
- let available_checks =
- [
- snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Type_of);
- snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Dead_end);
- snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Nested_strings);
- snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Locations);
- snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Dup_test);
- snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Write_only);
- ]
- let pp_module formatter (module A : Qsp_syntax.S.Analyzer) =
- Format.fprintf formatter "%s" A.identifier;
- Format.pp_print_tab formatter ();
- (match !A.active with
- | true -> Format.fprintf formatter "*"
- | false -> Format.fprintf formatter " ");
- Format.pp_print_tab formatter ();
- Format.fprintf formatter "%s" A.description;
- ()
- (** Print all the available modules *)
- let pp_modules formatter =
- let max_length =
- List.fold_left available_checks ~init:0 ~f:(fun l v ->
- let (module A : Qsp_syntax.S.Analyzer) =
- Qsp_syntax.Check.get_module v
- in
- max l (String.length A.identifier))
- in
- Format.pp_open_tbox formatter ();
- (* Print the name, left justified, with enought spaces for the all the
- identifiers *)
- Format.fprintf formatter "%-*s" (succ max_length) "Name";
- (* Tab delimiter *)
- Format.pp_set_tab formatter ();
- Format.fprintf formatter "Active ";
- Format.pp_set_tab formatter ();
- Format.fprintf formatter "Description@\n";
- Format.fprintf formatter "%a"
- (Format.pp_print_list
- (fun f v ->
- let m = Qsp_syntax.Check.get_module v in
- pp_module f m)
- ~pp_sep:(fun f () -> Format.pp_force_newline f ()))
- available_checks;
- Format.pp_close_tbox formatter ();
- Format.pp_print_break formatter 0 0
- (** Get all the tests to apply.
- The expression is declared lazy in order to be sure to apply the filters
- from the command line before. *)
- let checkers : (module Qsp_syntax.S.Analyzer) Lazy.t =
- lazy
- (let module Check = Qsp_syntax.Check.Make (struct
- let t =
- List.filter available_checks ~f:(fun v ->
- let (module A : Qsp_syntax.S.Analyzer) =
- Qsp_syntax.Check.get_module v
- in
- !A.active)
- |> Array.of_list
- end) in
- (module Check))
- (** Read the source file until getting a report (the whole location has been
- read properly), or until the first syntax error.
- The function update the context (list of errors) passed in arguments. *)
- let parse_location :
- type context.
- ctx:ctx ref ->
- (module Qsp_syntax.S.Analyzer with type context = context) ->
- context ->
- Qparser.Lexbuf.t ->
- Args.filters ->
- unit =
- fun ~ctx (module Check) context lexbuf filters ->
- let result =
- Qparser.Analyzer.parse (module Check) lexbuf context
- |> Result.map (fun f ->
- List.fold_left f.Qparser.Analyzer.report ~init:[]
- ~f:(filter_report filters)
- |> List.sort ~cmp:Report.compare)
- in
- match result with
- | Ok [] -> ()
- | Ok report ->
- (* Display the result *)
- let start_position, _ = Qparser.Lexbuf.positions lexbuf in
- Format.fprintf Format.std_formatter "Location@ %s@;@[%a@]@."
- start_position.Lexing.pos_fname Report.pp_result report;
- List.iter report ~f:(fun report ->
- match report.Report.level with
- | Error -> ctx := { !ctx with error_nb = succ !ctx.error_nb }
- | Warn -> ctx := { !ctx with warn_nb = succ !ctx.warn_nb }
- | Debug -> ctx := { !ctx with debug_nb = succ !ctx.debug_nb })
- | Error e ->
- (* Syntax error, we haven’t been able to run the test *)
- let start_position, _ = Qparser.Lexbuf.positions lexbuf in
- Format.fprintf Format.std_formatter "Location@ %s@;@[%a]@."
- start_position.Lexing.pos_fname Report.pp e;
- ctx := { !ctx with error_nb = succ !ctx.error_nb; fatal_error = true }
- let () =
- let file_names, parameters =
- Args.parse ~modules:available_checks ~list_tests:pp_modules
- in
- let file_name =
- List.filter ~f:(fun name -> name.[0] != '+') file_names |> List.hd
- in
- let ic = Stdlib.open_in_bin file_name in
- (*let lexer = Lexing.from_channel ~with_positions:true ic in*)
- let lexer, parameters =
- match Filename.extension file_name with
- | ".qsrc" ->
- (* Deactivate the tests which only applies to a global file *)
- List.iter available_checks ~f:(fun t ->
- let (module C : Qsp_syntax.S.Analyzer) =
- Qsp_syntax.Check.get_module t
- in
- if C.is_global && !C.active then C.active := false);
- (* The source file are in UTF-8, and we can use the file line number as
- we have only a single location. *)
- ( Sedlexing.Utf8.from_channel ic,
- { parameters with reset_line = parameters.Args.reset_line || false }
- )
- | ".txt" ->
- (Sedlexing.Utf16.from_channel ic (Some Little_endian), parameters)
- | _ -> raise (Failure "unknown extension")
- in
- let lexer =
- Qparser.Lexbuf.from_lexbuf ~reset_line:parameters.reset_line lexer
- in
- (* Initialize all the checkers before parsing the source *)
- let (module Check) = Lazy.force checkers in
- let check_context = Check.initialize () in
- let ctx =
- ref { error_nb = 0; warn_nb = 0; debug_nb = 0; fatal_error = false }
- in
- let () =
- try
- while true do
- parse_location ~ctx
- (module Check)
- check_context lexer parameters.filters
- done
- with Qparser.Lexer.EOF -> ()
- in
- (match !ctx.fatal_error with
- | true ->
- Format.fprintf Format.std_formatter
- "(Ignoring global checkers because of the previous syntax errors)@."
- | false ->
- (* If the parsing was global and we didn’t got parsing error, extract the
- result for the whole test *)
- let global_report = Check.finalize check_context in
- List.iter global_report ~f:(fun (f_name, report) ->
- Format.fprintf Format.std_formatter "Location@ %s@;@[%a@]@." f_name
- Report.pp report;
- match report.Report.level with
- | Error -> ctx := { !ctx with error_nb = succ !ctx.error_nb }
- | Warn -> ctx := { !ctx with warn_nb = succ !ctx.warn_nb }
- | Debug -> ctx := { !ctx with debug_nb = succ !ctx.debug_nb }));
- match (!ctx.error_nb, !ctx.warn_nb) with
- | 0, 0 -> (
- print_endline "No errors found";
- match !ctx.debug_nb with 0 -> exit 0 | _ -> exit 1)
- | _ ->
- Printf.printf "Found %d error(s), %d warning(s)\n" !ctx.error_nb
- !ctx.warn_nb;
- exit 1
|