diff --git a/src/config/config.ml b/src/config/config.ml new file mode 100644 index 00000000..8192ffb9 --- /dev/null +++ b/src/config/config.ml @@ -0,0 +1,231 @@ +(***************************************************************************) +(* *) +(* Copyright (c) 2014-2025 LexiFi SAS. All rights reserved. *) +(* *) +(* This source code is licensed under the MIT License *) +(* found in the LICENSE file at the root of this source tree *) +(* *) +(***************************************************************************) + +module Sections = Sections + +let must_report_section = Sections.must_report_section + +let has_activated = Sections.has_activated + +let must_report_call_sites = Sections.must_report_call_sites + +let get_main_threshold = Sections.get_main_threshold + +type t = + { verbose : bool + ; internal : bool + ; underscore : bool + ; paths_to_analyze : Utils.StringSet.t + ; excluded_paths : Utils.StringSet.t + ; references_paths : Utils.StringSet.t + ; sections : Sections.t + } + +let default_config = + { verbose = false + ; internal = false + ; underscore = false + ; paths_to_analyze = Utils.StringSet.empty + ; excluded_paths = Utils.StringSet.empty + ; references_paths = Utils.StringSet.empty + ; sections = Sections.default + } + +let must_report_main config = + let sections = config.sections in + has_activated [sections.exported_values; sections.methods; sections.types] + +let must_report_opt_args config = + let sections = config.sections in + has_activated [sections.opta; sections.optn] + +let update_exported_values arg config = + let sections = Sections.update_exported_values arg config.sections in + {config with sections} + +let update_methods arg config = + let sections = Sections.update_methods arg config.sections in + {config with sections} + +let update_types arg config = + let sections = Sections.update_types arg config.sections in + {config with sections} + +let update_opta arg config = + let sections = Sections.update_opta arg config.sections in + {config with sections} + +let update_optn arg config = + let sections = Sections.update_optn arg config.sections in + {config with sections} + +let update_style arg config = + let sections = Sections.update_style arg config.sections in + {config with sections} + +let set_verbose config = {config with verbose = true} + +(* Print name starting with '_' *) +let set_underscore config = {config with underscore = true} + +let set_internal config = {config with internal = true} + + +let normalize_path path = + (* remove redundant "." and consecutive dir_sep in path. + E.g. "./foo//bar/./baz" becomes "foo/bar/baz" *) + let split_path path = + let is_end_of_path path = + String.equal path Filename.current_dir_name + || String.equal path (Filename.dirname path) + in + let rec split_path path = + if is_end_of_path path then [path] + else + let splitted_dirpath = split_path (Filename.dirname path) in + (Filename.basename path) :: splitted_dirpath + in + List.rev (split_path path) + in + let remove_redundancies splitted_path = + let reject_empty_and_curr s = + String.equal s "" || String.equal s Filename.current_dir_name + in + List.filter reject_empty_and_curr splitted_path + in + let concat_path splitted_path = + String.concat Filename.dir_sep splitted_path + in + match path |> split_path |> remove_redundancies |> concat_path with + | "" -> Filename.current_dir_name + | normalized_path -> normalized_path + +let exclude path config = + let path = normalize_path path in + let excluded_paths = Utils.StringSet.add path config.excluded_paths in + {config with excluded_paths} + +let is_excluded path config = + let path = normalize_path path in + Utils.StringSet.mem path config.excluded_paths + +let add_reference_path path config = + let references_paths = Utils.StringSet.add path config.references_paths in + {config with references_paths} + +let add_path_to_analyze path config = + let paths_to_analyze = Utils.StringSet.add path config.paths_to_analyze in + {config with paths_to_analyze} + +(* Command line parsing *) +let parse_cli () = + let config = ref default_config in + let update_config f x = config := f x !config in + let update_config_unit f () = config := f !config in + + let update_all arg config = + config + |> update_style ((if arg = "all" then "+" else "-") ^ "all") + |> update_exported_values arg + |> update_methods arg + |> update_types arg + |> update_opta arg + |> update_optn arg + in + + Arg.(parse + [ "--exclude", + String (update_config exclude), + " Exclude given path from research." + + ; "--references", + String (update_config add_reference_path), + " Consider given path to collect references." + + ; "--underscore", + Unit (update_config_unit set_underscore), + " Show names starting with an underscore" + + ; "--verbose", + Unit (update_config_unit set_verbose), + " Verbose mode (ie., show scanned files)" + ; "-v", Unit (update_config_unit set_verbose), " See --verbose" + + ; "--internal", + Unit (update_config_unit set_internal), + " Keep internal uses as exported values uses when the interface is given. \ + This is the default behaviour when only the implementation is found" + + ; "--nothing", + Unit (update_config_unit (update_all "nothing")), + " Disable all warnings" + ; "-a", Unit (update_config_unit (update_all "nothing")), " See --nothing" + + ; "--all", + Unit (update_config_unit (update_all "all")), + " Enable all warnings" + ; "-A", Unit (update_config_unit (update_all "all")), " See --all" + + ; "-E", + String (update_config update_exported_values), + " Enable/Disable unused exported values warnings.\n \ + can be:\n\ + \tall\n\ + \tnothing\n\ + \t\"threshold:\": report elements used up to the given integer\n\ + \t\"calls:\": like threshold + show call sites" + + ; "-M", + String (update_config update_methods), + " Enable/Disable unused methods warnings.\n \ + See option -E for the syntax of " + + ; "-Oa", + String (update_config update_opta), + " Enable/Disable optional arguments always used warnings.\n \ + can be:\n\ + \tall\n\ + \tnothing\n\ + \t\n\ + \t\"calls:\" like + show call sites\n \ + can be:\n\ + \t\"both:,\": both the number max of exceptions \ + (given through the integer) and the percent of valid cases (given as a float) \ + must be respected for the element to be reported\n\ + \t\"percent:\": percent of valid cases to be reported" + + ; "-On", + String (update_config update_optn), + " Enable/Disable optional arguments never used warnings.\n \ + See option -Oa for the syntax of " + + ; "-S", + String (update_config update_style), + " Enable/Disable coding style warnings.\n \ + Delimiters '+' and '-' determine if the following option is to enable or disable.\n \ + Options (can be used together):\n\ + \tbind: useless binding\n\ + \topt: optional arg in arg\n\ + \tseq: use sequence\n\ + \tunit: unit pattern\n\ + \tall: bind & opt & seq & unit" + + ; "-T", + String (update_config update_types), + " Enable/Disable unused constructors/records fields warnings.\n \ + See option -E for the syntax of " + + ] + ( + update_config add_path_to_analyze + ) + ("Usage: " ^ Sys.argv.(0) ^ " \nOptions are:") + ); + + !config diff --git a/src/config/config.mli b/src/config/config.mli new file mode 100644 index 00000000..cca086bd --- /dev/null +++ b/src/config/config.mli @@ -0,0 +1,57 @@ +(** Configuration of the analyzer *) + +(** {2 Sections configuration} *) + +module Sections = Sections + +val must_report_section : _ Sections.section -> bool +(** [must_report_section sec] returns `true` if the section must be reported *) + +val must_report_call_sites : _ Sections.section -> bool +(** [must_report_call_sites sec] returns `true` if call sites must be reported in + thresholded subsections *) + +val get_main_threshold : Sections.main_section -> int +(** [get_main_threshold main_sec] returns the threshold if + [main_sec = Threshold _], [0] otherwise. *) + +(** {2 General configuration} *) + +type t = private + { verbose : bool (** Display additional information during the analaysis *) + ; internal : bool (** Keep track of internal uses for exported values *) + ; underscore : bool (** Keep track of elements with names starting with [_] *) + ; paths_to_analyze : Utils.StringSet.t + (** Paths found in the command line and considered for analysis *) + ; excluded_paths : Utils.StringSet.t (** Paths to exclude from the analysis *) + ; references_paths : Utils.StringSet.t + (** Paths to explore for references only *) + ; sections : Sections.t (** Config for the different report sections *) + } + +val default_config : t +(** Default configuration for the analysis. + By default [verbose], [internal], and [underscore] are [false] + By default [paths_to_analyze], [excluded_paths], and [references_paths] are empty. + By default [sections] is [Sections.default] *) + +val must_report_main : t -> bool +(** [must_report_main config] indicates if any of the main sections + is activated in [config] *) + +val must_report_opt_args : t -> bool +(** [must_report_opt_args config] indicates if any of the optional + arguments section is activated in [config] *) + +val update_style : string -> t -> t +(** [update_style arg config] returns a [config] with style section + configuration updated according to the [arg] specification. *) + +val is_excluded : string -> t -> bool +(** [is_excluded path config] indicates if [path] is excluded from the analysis + in [config]. + Excluding a path is done with the --exclude command line argument. *) + +val parse_cli : unit -> t +(** [parse_cli ()] returns a fresh configuration filled up according to the + command line arguments *) diff --git a/src/config/sections.ml b/src/config/sections.ml new file mode 100644 index 00000000..b6483659 --- /dev/null +++ b/src/config/sections.ml @@ -0,0 +1,195 @@ +type t = + { exported_values : main_section + ; methods : main_section + ; types : main_section + ; opta : opt_args_section + ; optn : opt_args_section + ; style : style_section + } + +and main_section = int section + +and opt_args_section = opt_args_threshold section +and opt_args_threshold = + | Percent of float + | Both of (int * float) + +and 'threshold section = + | Off + | On + | Threshold of 'threshold thresholded_section + +and 'threshold thresholded_section = + { threshold: 'threshold + ; call_sites: bool + } + +and style_section = + { opt_arg: bool + ; unit_pat: bool + ; seq: bool + ; binding: bool + } + + +let default = + { exported_values = On + ; methods = On + ; types = On + ; opta = Off + ; optn = Off + ; style = + { opt_arg = false + ; unit_pat = false + ; seq = false + ; binding = false + } + } + +let must_report_section = function + | Off -> false + | On | Threshold _ -> true + +let has_activated l = + List.exists must_report_section l + +let must_report_call_sites = function + | Threshold {call_sites; _} -> call_sites + | On | Off -> false + +let get_main_threshold = function + | Threshold {threshold; _} -> threshold + | On | Off -> 0 + +let parse_main_section main_arg = function + | "all" -> On + | "nothing" -> Off + | arg -> + let raise_bad_arg msg = + raise (Arg.Bad (main_arg ^ ": " ^ msg)) + in + let threshold_section = + let call_sites, threshold = + let len = String.length arg in + if String.starts_with ~prefix:"calls:" arg then + (true, String.sub arg 6 (len - 6)) + else if String.starts_with ~prefix:"threshold:" arg then + (false, String.sub arg 10 (len - 10)) + else raise_bad_arg ("unknown option: " ^ arg) + in + match String.trim threshold |> int_of_string with + | exception Failure _ -> + raise_bad_arg ("expected an integer; got; Got " ^ threshold) + | n when n < 0 -> + raise_bad_arg ("integer should be >= 0; Got " ^ string_of_int n) + | threshold -> {threshold; call_sites} + in + Threshold threshold_section + +let update_exported_values arg sections = + let exported_values = parse_main_section "-E" arg in + {sections with exported_values} + +let update_methods arg sections = + let methods = parse_main_section "-M" arg in + {sections with methods} + +let update_types arg sections = + let types = parse_main_section "-T" arg in + {sections with types} + + +let parse_opt_section = function + | "all" -> On + | "nothing" -> Off + | arg -> + let raise_bad_arg msg = + (* TODO: improve error reporting *) + raise (Arg.Bad ("-Ox: " ^ msg)) + in + let call_sites, arg = + if String.starts_with ~prefix:"calls" arg then + let arg = String.sub arg 6 (String.length arg - 6) in + (true, arg) + else (false, arg) + in + let check_percentage p = + if p > 1. || p < 0. then + raise_bad_arg "percentage must be >= 0.0 and <= 1.0" + in + let check_nb_exceptions n = + if n < 0 then raise_bad_arg "number of exceptions must be >= 0" + in + let threshold = + let len = String.length arg in + if String.starts_with ~prefix:"both:" arg then + let limits = String.sub arg 5 (len - 5) in + match Scanf.sscanf limits "%u , %F" (fun i f -> (i, f)) with + | exception Scanf.Scan_failure _ + | exception Failure _ + | exception End_of_file -> + (* TODO: improve error handling/reporting *) + raise_bad_arg ("wrong arguments: " ^ limits) + | (nb_exceptions, percentage) as limits -> + check_percentage percentage; + check_nb_exceptions nb_exceptions; + Both limits + else if String.starts_with ~prefix:"percent:" arg then + let percentage = String.sub arg 8 (len - 8) |> String.trim in + match float_of_string percentage with + | exception Failure _ -> + (* TODO: improve error handling/reporting *) + raise_bad_arg ("wrong argument: " ^ percentage) + | percentage -> + check_percentage percentage; + Percent percentage + else raise_bad_arg ("unknown option " ^ arg) + in + Threshold {threshold; call_sites} + +let update_opta arg sections = + let opta = parse_opt_section arg in + {sections with opta} + +let update_optn arg sections = + let optn = parse_opt_section arg in + {sections with optn} + + +let update_style arg style = + let rec aux style = function + | (b, "opt")::l -> + let style = {style with opt_arg = b} in + aux style l + | (b, "unit")::l -> + let style = {style with unit_pat = b} in + aux style l + | (b, "seq")::l -> + let style = {style with seq = b} in + aux style l + | (b, "bind")::l -> + let style = {style with binding = b} in + aux style l + | (b, "all")::l -> + let style = {unit_pat = b; opt_arg = b; seq = b; binding = b} in + aux style l + | (_, "")::l -> aux style l + | (_, s)::_ -> raise (Arg.Bad ("-S: unknown option: " ^ s)) + | [] -> style + in + let list_of_opt arg = + try + let rec split acc pos len = + if arg.[pos] <> '+' && arg.[pos] <> '-' then + split acc (pos - 1) (len + 1) + else let acc = (arg.[pos] = '+', String.trim (String.sub arg (pos + 1) len)) :: acc in + if pos > 0 then split acc (pos - 1) 0 + else acc + in split [] (String.length arg - 1) 0 + with _ -> raise (Arg.Bad ("options' arguments must start with a delimiter (`+' or `-')")) + in + aux style (list_of_opt arg) + +let update_style arg sections = + let style = update_style arg sections.style in + {sections with style} diff --git a/src/config/sections.mli b/src/config/sections.mli new file mode 100644 index 00000000..75094d15 --- /dev/null +++ b/src/config/sections.mli @@ -0,0 +1,92 @@ +type t = private + { exported_values : main_section (** Exported values section config *) + ; methods : main_section (** Methods section config *) + ; types : main_section (** Constructors/fields section config *) + ; opta : opt_args_section (** Opt args always used section config *) + ; optn : opt_args_section (** Opt args always used section config *) + ; style : style_section (** Stylistic issues section config *) + } + +and main_section = int section + +and opt_args_section = opt_args_threshold section +and opt_args_threshold = + | Percent of float + (** Subsections for opt args always/never used at least [float] percent of + the time will be reported *) + | Both of (int * float) + (** Subsections for opt args always/never used with at most [int] + exceptions and at least [float] percent of the time will be reported *) + +and 'threshold section = + | Off (** Disabled *) + | On (** Enabled *) + | Threshold of 'threshold thresholded_section + (** Report elements up to [!'threshold] *) + +and 'threshold thresholded_section = + { threshold: 'threshold + (** Report subsections for elements used up to [!threshold] *) + ; call_sites: bool + (** Print call sites in the [!threshold]-related subsections *) + } + +and style_section = + { opt_arg: bool (** Report [val f : _ -> (... -> (... -> ?_:_ -> ...) -> ...] *) + ; unit_pat: bool (** Report unit pattern *) + ; seq: bool (** Report [let () = ... in ... (=> use sequence)] *) + ; binding: bool (** Report [let x = ... in x (=> useless binding)] *) + } + +val default : t +(** Default sections configuration. + [exported_values], [methods], and [types] are [On]. + [opta], [optn] are [Off]. + All of the fields in [style] are false. *) + +val must_report_section : _ section -> bool +(** [must_report_section sec] returns [true] if the section must be reported *) + +val has_activated : _ section list -> bool +(** [has_activated secs] returns [true] if one of the sections in [sec] is + activated *) + +val must_report_call_sites : _ section -> bool +(** [must_report_call_sites sec] returns [true] if call sites must be reported in + thresholded subsections *) + + +val get_main_threshold : int section -> int +(** [get_main_threshold main_sec] returns the threshold if + [main_sec = Threshold _], [0] otherwise. *) + + +val update_exported_values : string -> t -> t +(** [update_exported_values arg sections] configures the [exported_values] + section according to [arg] and returns an updated version of [sections]. + [arg]'s specification is the one for the command line option "-E" *) + +val update_methods : string -> t -> t +(** [update_exported_values arg sections] configures the [methods] + section according to [arg] and returns an updated version of [sections] + [arg]'s specification is the one for the command line option "-M" *) + +val update_types : string -> t -> t +(** [update_exported_values arg sections] configures the [types] + section according to [arg] and returns an updated version of [sections] + [arg]'s specification is the one for the command line option "-T" *) + +val update_opta : string -> t -> t +(** [update_exported_values arg sections] configures the [opta] + section according to [arg] and returns an updated version of [sections] + [arg]'s specification is the one for the command line option "-Oa" *) + +val update_optn : string -> t -> t +(** [update_exported_values arg sections] configures the [optn] + section according to [arg] and returns an updated version of [sections] + [arg]'s specification is the one for the command line option "-On" *) + +val update_style : string -> t -> t +(** [update_exported_values arg sections] configures the [style] + section according to [arg] and returns an updated version of [sections] + [arg]'s specification is the one for the command line option "-S" *) diff --git a/src/deadArg.ml b/src/deadArg.ml index 52216448..566dbaed 100644 --- a/src/deadArg.ml +++ b/src/deadArg.ml @@ -154,6 +154,7 @@ let register_uses val_loc args = register_uses builddir val_loc args let rec bind loc expr = + let state = State.get_current () in match expr.exp_desc with | Texp_function (params, body) -> ( let check_param_style = function @@ -162,7 +163,8 @@ let rec bind loc expr = DeadType.check_style pat_type expr.exp_loc.Location.loc_start in let register_optional_param = function - | Asttypes.Optional s when DeadFlag.(!optn.print || !opta.print) -> + | Asttypes.Optional s + when Config.must_report_opt_args state.config -> let (opts, next) = VdNode.get loc in VdNode.update loc (s :: opts, next) | _ -> () @@ -181,7 +183,7 @@ let rec bind loc expr = | _ -> () ) | exp_desc - when (!DeadFlag.optn.print || !DeadFlag.opta.print) + when Config.must_report_opt_args state.config && DeadType.nb_args ~keep:`Opt expr.exp_type > 0 -> let ( let$ ) x f = Option.iter f x in let$ loc2 = @@ -198,6 +200,9 @@ let rec bind loc expr = (******** WRAPPING ********) let wrap f x y = - if DeadFlag.(!optn.print || !opta.print) then f x y else () + let state = State.get_current () in + if Config.must_report_opt_args state.config then + f x y + else () let register_uses val_loc args = wrap register_uses val_loc args diff --git a/src/deadCode.ml b/src/deadCode.ml index f64b7e5a..8e3393e1 100644 --- a/src/deadCode.ml +++ b/src/deadCode.ml @@ -35,8 +35,9 @@ let rec collect_export ?(mod_type = false) path u stock = function | Sig_value (id, ({Types.val_loc; val_type; _} as value), _) when not val_loc.Location.loc_ghost -> + let state = State.get_current () in let should_export stock loc = - !DeadFlag.exported.DeadFlag.print + Config.must_report_section state.config.sections.exported_values && (* do not add the loc in decs if it belongs to a module type *) ( stock != decs || not (Hashtbl.mem in_modtype loc.Location.loc_start) @@ -129,14 +130,16 @@ let value_binding super self x = let structure_item super self i = let state = State.get_current () in + let sections = state.config.sections in let open Asttypes in begin match i.str_desc with - | Tstr_type (_, l) when !DeadFlag.typ.DeadFlag.print -> + | Tstr_type (_, l) when Config.must_report_section sections.types -> List.iter DeadType.tstr l | Tstr_module {mb_name = {txt = Some txt; _}; _} -> mods := txt :: !mods; DeadMod.defined := String.concat "." (List.rev !mods) :: !DeadMod.defined - | Tstr_class l when !DeadFlag.obj.DeadFlag.print -> List.iter DeadObj.tstr l + | Tstr_class l when Config.must_report_section sections.methods -> + List.iter DeadObj.tstr l | Tstr_include i -> let collect_include signature = let prev_last_loc = !last_loc in @@ -172,18 +175,20 @@ let structure_item super self i = let pat: type k. Tast_mapper.mapper -> Tast_mapper.mapper -> k general_pattern -> k general_pattern = fun super self p -> + let state = State.get_current () in + let sections = state.config.sections in let pat_loc = p.pat_loc.Location.loc_start in let u s = register_style pat_loc (Printf.sprintf "unit pattern %s" s) in let open Asttypes in - if DeadType.is_unit p.pat_type && !DeadFlag.style.DeadFlag.unit_pat then begin + if DeadType.is_unit p.pat_type && sections.style.unit_pat then begin match p.pat_desc with | Tpat_construct _ -> () | Tpat_var (_, {txt = "eta"; loc = _}, _) when p.pat_loc = Location.none -> () | Tpat_var (_, {txt; _}, _) -> if check_underscore txt then u txt - | Tpat_any -> if not !DeadFlag.underscore then u "_" + | Tpat_any -> if state.config.underscore then u "_" | Tpat_value tpat_arg -> begin match (tpat_arg :> value general_pattern) with | {pat_desc=Tpat_construct _; _} -> () @@ -195,7 +200,7 @@ let pat: type k. Tast_mapper.mapper -> Tast_mapper.mapper -> k general_pattern - | Tpat_record (l, _) -> List.iter (fun (_, {Types.lbl_loc = {Location.loc_start = lab_loc; _}; _}, _) -> - if exported DeadFlag.typ lab_loc then + if exported ~is_type:true sections.types lab_loc then DeadType.collect_references lab_loc pat_loc ) l @@ -205,6 +210,8 @@ let pat: type k. Tast_mapper.mapper -> Tast_mapper.mapper -> k general_pattern - let expr super self e = + let state = State.get_current () in + let sections = state.config.sections in let rec extra = function | [] -> () | (Texp_coerce (_, typ), _, _)::l -> DeadObj.coerce e typ.ctyp_type; extra l @@ -218,12 +225,12 @@ let expr super self e = !DeadLexiFi.ttype_of e | Texp_ident (_, _, {Types.val_loc = {Location.loc_start = loc; loc_ghost = false; _}; _}) - when exported DeadFlag.exported loc -> + when exported sections.exported_values loc -> LocHash.add_set references loc exp_loc | Texp_field (_, _, {lbl_loc = {Location.loc_start = loc; loc_ghost = false; _}; _}) | Texp_construct (_, {cstr_loc = {Location.loc_start = loc; loc_ghost = false; _}; _}, _) - when exported DeadFlag.typ loc -> + when exported ~is_type:true sections.types loc -> DeadType.collect_references loc exp_loc | Texp_send (e2, Tmeth_name meth) -> @@ -234,7 +241,8 @@ let expr super self e = | Texp_apply (exp, args) -> - if DeadFlag.(!opta.print || !optn.print) then treat_exp exp args; + if Config.must_report_opt_args state.config then + treat_exp exp args; begin match exp.exp_desc with | Texp_ident (_, _, {Types.val_loc; _}) when val_loc.Location.loc_ghost -> (* The node is due to lookup preparation @@ -248,7 +256,7 @@ let expr super self e = end | Texp_let (_, [{vb_pat; _}], _) - when DeadType.is_unit vb_pat.pat_type && !DeadFlag.style.DeadFlag.seq -> + when DeadType.is_unit vb_pat.pat_type && sections.style.seq -> begin match vb_pat.pat_desc with | Tpat_var (id, _, _) when not (check_underscore (Ident.name id)) -> () | _ -> @@ -258,7 +266,7 @@ let expr super self e = end | Texp_match (_, [{c_lhs; _}], _) - when DeadType.is_unit c_lhs.pat_type && !DeadFlag.style.DeadFlag.seq -> + when DeadType.is_unit c_lhs.pat_type && sections.style.seq -> begin match c_lhs.pat_desc with | Tpat_value tpat_arg -> begin match (tpat_arg :> value general_pattern) with @@ -276,7 +284,7 @@ let expr super self e = [{vb_pat = {pat_desc = Tpat_var (id1, _, _); pat_loc = {loc_start = loc; _}; _}; _}], {exp_desc = Texp_ident (Path.Pident id2, _, _); exp_extra = []; _}) when id1 = id2 - && !DeadFlag.style.DeadFlag.binding + && sections.style.binding && check_underscore (Ident.name id1) -> register_style loc "let x = ... in x (=> useless binding)" @@ -336,10 +344,11 @@ let collect_references = (* Tast_mapper *) (* Checks the nature of the file *) let kind fn = + let state = State.get_current () in if not (Sys.file_exists fn) then begin prerr_endline ("Warning: '" ^ fn ^ "' not found"); `Ignore - end else if DeadFlag.is_excluded fn then `Ignore + end else if Config.is_excluded fn state.config then `Ignore else if Sys.is_directory fn then `Dir else if Filename.check_suffix fn ".cmi" then `Cmi else if Filename.check_suffix fn ".cmt" then `Cmt @@ -356,10 +365,7 @@ let regabs state = let read_interface fn cmi_infos state = let open Cmi_format in try regabs state; - if !DeadFlag.exported.DeadFlag.print - || !DeadFlag.obj.DeadFlag.print - || !DeadFlag.typ.DeadFlag.print - then + if Config.must_report_main state.config then let u = if State.File_infos.has_sourcepath state.file_infos then State.File_infos.get_sourceunit state.file_infos @@ -397,7 +403,7 @@ let assoc decs (loc1, loc2) = || not (is_implem fn && has_iface fn) in if fn1 <> _none && fn2 <> _none && loc1 <> loc2 then begin - if (!DeadFlag.internal || fn1 <> fn2) && is_implem fn1 && is_implem fn2 then + if (state.config.internal || fn1 <> fn2) && is_implem fn1 && is_implem fn2 then DeadCommon.LocHash.merge_set references loc2 references loc1; if is_iface fn1 loc1 then begin if is_iface fn2 loc2 then @@ -440,7 +446,7 @@ let eof loc_dep = (* Starting point *) -let rec load_file state fn = +let rec load_file fn state = let init_and_continue state fn f = match State.change_file state fn with | Error msg -> @@ -455,7 +461,7 @@ let rec load_file state fn = match kind fn with | `Cmi when !DeadCommon.declarations -> last_loc := Lexing.dummy_pos; - if !DeadFlag.verbose then Printf.eprintf "Scanning %s\n%!" fn; + if state.State.config.verbose then Printf.eprintf "Scanning %s\n%!" fn; init_and_continue state fn (fun state -> match state.file_infos.cmi_infos with | None -> () (* TODO error handling ? *) @@ -465,7 +471,7 @@ let rec load_file state fn = | `Cmt -> let open Cmt_format in last_loc := Lexing.dummy_pos; - if !DeadFlag.verbose then Printf.eprintf "Scanning %s\n%!" fn; + if state.config.verbose then Printf.eprintf "Scanning %s\n%!" fn; init_and_continue state fn (fun state -> regabs state; match state.file_infos.cmt_infos with @@ -483,7 +489,7 @@ let rec load_file state fn = ignore (collect_references.Tast_mapper.structure collect_references x); let loc_dep = - if !DeadFlag.exported.DeadFlag.print then + if Config.must_report_section state.config.sections.exported_values then List.rev_map (fun (vd1, vd2) -> (vd1.Types.val_loc.Location.loc_start, vd2.Types.val_loc.Location.loc_start) @@ -499,7 +505,7 @@ let rec load_file state fn = let next = Sys.readdir fn in Array.sort compare next; Array.fold_left - (fun state s -> load_file state (fn ^ "/" ^ s)) + (fun state s -> load_file (fn ^ "/" ^ s) state) state next (* else Printf.eprintf "skipping directory %s\n" fn *) @@ -545,19 +551,26 @@ let analyze_opt_args () = let report_opt_args s l = + let state = State.get_current () in let opt = - if s = "NEVER" then !DeadFlag.optn - else !DeadFlag.opta + if s = "NEVER" then state.config.sections.optn + else state.config.sections.opta in - let percent = percent opt in let rec report_opt_args nb_call = - let open DeadFlag in let l = List.filter (fun (_, _, _, slot, ratio, _) -> let ratio = 1. -. ratio in - if opt.threshold.optional = `Both then - ratio >= opt.threshold.percentage && check_length nb_call slot - else ratio >= percent nb_call - && (opt.threshold.percentage >= 1. || ratio < (percent (nb_call - 1)))) + match opt with + | Off -> + (* TODO: better error handling *) + failwith "Trying to report a deactivated opt args section" + | On -> ratio >= 1. && nb_call = 0 + | Threshold {threshold = Both (_, percentage); _} -> + ratio >= percentage && check_length nb_call slot + | Threshold {threshold = Percent percentage as threshold; _} -> + let percent = percent threshold in + + ratio >= percent nb_call + && (percentage >= 1. || ratio < (percent (nb_call - 1)))) @@ List.map (fun (builddir, loc, lab, slot) -> let l = if s = "NEVER" then slot.with_val else slot.without_val in @@ -586,18 +599,22 @@ let report_opt_args s l = prloc ~fn loc; print_string ("?" ^ lab); if ratio <> 0. then begin Printf.printf " (%d/%d calls)" (total - List.length slot) total; - if opt.call_sites then print_string " Exceptions:" + if Config.must_report_call_sites opt then print_string " Exceptions:" end; print_newline (); - if opt.call_sites then begin + if Config.must_report_call_sites opt then begin List.iter (pretty_print_call ()) slot; if nb_call <> 0 then print_newline () end in let continue nb_call = - opt.threshold.optional = `Both && nb_call < opt.threshold.exceptions - || opt.threshold.optional = `Percent && percent nb_call > opt.threshold.percentage + match opt with + | Off | On -> false + | Threshold {threshold = Both (exceptions, _); _} -> + nb_call < exceptions + | Threshold {threshold = Percent percentage as threshold; _} -> + percent threshold nb_call > percentage in let s = (if nb_call > 0 then "OPTIONAL ARGUMENTS: ALMOST " @@ -608,7 +625,12 @@ let report_opt_args s l = in report_opt_args 0 -let report_unused_exported () = report_basic decs "UNUSED EXPORTED VALUES" !DeadFlag.exported +let report_unused_exported () = + let state = State.get_current () in + report_basic + decs + "UNUSED EXPORTED VALUES" + state.config.sections.exported_values let report_style () = @@ -630,122 +652,53 @@ let report_style () = (* Option parsing and processing *) -let parse () = - let update_all print () = - DeadFlag.( - update_style ((if print = "all" then "+" else "-") ^ "all"); - update_basic "-E" DeadFlag.exported print; - update_basic "-M" obj print; - update_basic "-T" typ print; - update_opt opta print; - update_opt optn print) +let run_analysis state = + let process_file filename state = + let state = load_file filename state in + State.update state; + state in - - let load_file filename = - let state = State.get_current () in - let state = load_file state filename in - State.update state - in - - (* any extra argument can be accepted by any option using some - * although it doesn't necessary affects the results (e.g. -O 3+4) *) - Arg.(parse - [ "--exclude", String DeadFlag.exclude, " Exclude given path from research."; - - "--references", - String (fun dir -> DeadFlag.directories := dir :: !DeadFlag.directories), - " Consider given path to collect references."; - - "--underscore", Unit DeadFlag.set_underscore, " Show names starting with an underscore"; - - "--verbose", Unit DeadFlag.set_verbose, " Verbose mode (ie., show scanned files)"; - "-v", Unit DeadFlag.set_verbose, " See --verbose"; - - "--internal", Unit DeadFlag.set_internal, - " Keep internal uses as exported values uses when the interface is given. \ - This is the default behaviour when only the implementation is found"; - - "--nothing", Unit (update_all "nothing"), " Disable all warnings"; - "-a", Unit (update_all "nothing"), " See --nothing"; - "--all", Unit (update_all "all"), " Enable all warnings"; - "-A", Unit (update_all "all"), " See --all"; - - "-E", String (DeadFlag.update_basic "-E" DeadFlag.exported), - " Enable/Disable unused exported values warnings.\n \ - can be:\n\ - \tall\n\ - \tnothing\n\ - \t\"threshold:\": report elements used up to the given integer\n\ - \t\"calls:\": like threshold + show call sites"; - - "-M", String (DeadFlag.update_basic "-M" DeadFlag.obj), - " Enable/Disable unused methods warnings.\n \ - See option -E for the syntax of "; - - "-Oa", String (DeadFlag.update_opt DeadFlag.opta), - " Enable/Disable optional arguments always used warnings.\n \ - can be:\n\ - \tall\n\ - \tnothing\n\ - \t\n\ - \t\"calls:\" like + show call sites\n \ - can be:\n\ - \t\"both:,\": both the number max of exceptions \ - (given through the integer) and the percent of valid cases (given as a float) \ - must be respected for the element to be reported\n\ - \t\"percent:\": percent of valid cases to be reported"; - - "-On", String (DeadFlag.update_opt DeadFlag.optn), - " Enable/Disable optional arguments never used warnings.\n \ - See option -Oa for the syntax of "; - - "-S", String (DeadFlag.update_style), - " Enable/Disable coding style warnings.\n \ - Delimiters '+' and '-' determine if the following option is to enable or disable.\n \ - Options (can be used together):\n\ - \tbind: useless binding\n\ - \topt: optional arg in arg\n\ - \tseq: use sequence\n\ - \tunit: unit pattern\n\ - \tall: bind & opt & seq & unit"; - - "-T", String (DeadFlag.update_basic "-T" DeadFlag.typ), - " Enable/Disable unused constructors/records fields warnings.\n \ - See option -E for the syntax of "; - - ] - (Printf.eprintf "Scanning files...\n%!"; - load_file) - ("Usage: " ^ Sys.argv.(0) ^ " \nOptions are:")) - + Printf.eprintf "Scanning files...\n%!"; + Utils.StringSet.fold + process_file + state.State.config.paths_to_analyze + state let () = try - parse (); + let config = Config.parse_cli () in + let state = State.init config in + let state = run_analysis state in let run_on_references_only state = DeadCommon.declarations := false; - let oldstyle = !DeadFlag.style in - DeadFlag.update_style "-all"; - List.fold_left load_file state !DeadFlag.directories - |> ignore; - DeadFlag.style := oldstyle + let no_style_config = Config.update_style "-all" state.State.config in + let state = State.update_config no_style_config state in + let state = + Utils.StringSet.fold + load_file + state.config.references_paths + state + in + State.update_config config state in - run_on_references_only (State.get_current ()); + let state = run_on_references_only state in + State.update state; Printf.eprintf " [DONE]\n\n%!"; - let open DeadFlag in !DeadLexiFi.prepare_report DeadType.decs; - if !DeadFlag.exported.print then report_unused_exported (); + let sections = state.config.sections in + if Config.must_report_section sections.exported_values then report_unused_exported (); DeadObj.report(); DeadType.report(); - if !DeadFlag.opta.DeadFlag.print || !DeadFlag.optn.DeadFlag.print - then begin - let tmp = analyze_opt_args () in - if !DeadFlag.opta.print then report_opt_args "ALWAYS" tmp; - if !DeadFlag.optn.print then report_opt_args "NEVER" tmp end; - if [@warning "-44"] DeadFlag.(!style.opt_arg || !style.unit_pat - || !style.seq || !style.binding) then report_style (); + if Config.must_report_opt_args state.config then begin + let tmp = analyze_opt_args () in + if Config.must_report_section sections.opta then report_opt_args "ALWAYS" tmp; + if Config.must_report_section sections.optn then report_opt_args "NEVER" tmp + end; + let style = sections.style in + if style.opt_arg || style.unit_pat || style.seq || style.binding then + report_style (); if !bad_files <> [] then begin let oc = open_out_bin "remove_bad_files.sh" in diff --git a/src/deadCommon.ml b/src/deadCommon.ml index 83c10e66..bd9344ad 100644 --- a/src/deadCommon.ml +++ b/src/deadCommon.ml @@ -84,7 +84,9 @@ let is_ghost loc = || loc.Lexing.pos_fname = _none || loc.Lexing.pos_fname = "" -let check_underscore name = not !DeadFlag.underscore || name.[0] <> '_' +let check_underscore name = + let state = State.get_current () in + state.config.underscore || name.[0] <> '_' let hashtbl_find_list hashtbl key = Hashtbl.find_all hashtbl key @@ -140,15 +142,15 @@ let rec get_deep_desc typ = | t -> t -let exported (flag : DeadFlag.basic ref) loc = +let exported ?(is_type = false) (flag : Config.Sections.main_section) loc = let state = State.get_current () in let fn = loc.Lexing.pos_fname in let sourceunit = State.File_infos.get_sourceunit state.file_infos in - !flag.DeadFlag.print + Config.must_report_section flag && LocHash.find_set references loc - |> LocSet.cardinal <= !flag.DeadFlag.threshold - && (flag == DeadFlag.typ - || !DeadFlag.internal + |> LocSet.cardinal <= Config.get_main_threshold flag + && (is_type + || state.config.internal || fn.[String.length fn - 1] = 'i' || sourceunit <> Utils.unit fn || not (file_exists (fn ^ "i"))) @@ -432,20 +434,32 @@ let pretty_print_call () = let ghost = ref false in function ghost := true -let percent (opt : DeadFlag.opt) base = - let open DeadFlag in - 1. -. (float_of_int base) *. (1. -. opt.threshold.percentage) /. 10. +let percent (opt_threshold : Config.Sections.opt_args_threshold) base = + let percentage = + match opt_threshold with + | Percent p | Both (_, p) -> p + in + 1. -. (float_of_int base) *. (1. -. percentage) /. 10. (* Base pattern for reports *) -let report s ~(opt: DeadFlag.opt) ?(extra = "Called") l continue nb_call pretty_print reporter = +let report s ~(opt: Config.Sections.opt_args_section) ?(extra = "Called") l + continue nb_call pretty_print reporter += if nb_call = 0 || l <> [] then begin section ~sub:(nb_call <> 0) @@ (if nb_call = 0 then s - else if DeadFlag.(opt.threshold.optional) = `Both || extra = "Called" - then + else if String.equal extra "Called" then Printf.sprintf "%s: %s %d time(s)" s extra nb_call - else Printf.sprintf "%s: at least %3.2f%% of the time" s (100. *. percent opt nb_call)); + else match opt with + | Threshold {threshold = Both _; _} -> + Printf.sprintf "%s: %s %d time(s)" s extra nb_call + | Threshold {threshold; _} -> + let percent = 100. *. percent threshold nb_call in + Printf.sprintf "%s: at least %3.2f%% of the time" s percent + | _ -> + (* TODO: better error handling *) + failwith "Trying to report subsections but not threshold is found"); List.iter pretty_print l; if continue nb_call then (if l <> [] then print_endline "--------" else ()) |> print_newline |> print_newline @@ -454,7 +468,7 @@ let report s ~(opt: DeadFlag.opt) ?(extra = "Called") l continue nb_call pretty_ else (print_newline () |> separator) -let report_basic ?folder decs title (flag:DeadFlag.basic) = +let report_basic ?folder decs title (flag: Config.Sections.main_section) = let folder = match folder with | Some folder -> folder | None -> fun nb_call -> fun loc (builddir, path) acc -> @@ -497,22 +511,23 @@ let report_basic ?folder decs title (flag:DeadFlag.basic) = if change fn then print_newline (); prloc ~fn loc; print_string path; - if call_sites <> [] && flag.DeadFlag.call_sites then + if call_sites <> [] && Config.must_report_call_sites flag then print_string " Call sites:"; print_newline (); - if flag.DeadFlag.call_sites then begin + if Config.must_report_call_sites flag then begin List.fast_sort compare call_sites |> List.iter (pretty_print_call ()); if nb_call <> 0 then print_newline () end in - let continue nb_call = nb_call < flag.DeadFlag.threshold in + let continue nb_call = nb_call < Config.get_main_threshold flag in let s = if nb_call = 0 then title else "ALMOST " ^ title in - report s ~opt:(!DeadFlag.opta) l continue nb_call pretty_print reportn + let state = State.get_current () in + report s ~opt:(state.config.sections.opta) l continue nb_call pretty_print reportn in reportn 0 diff --git a/src/deadFlag.ml b/src/deadFlag.ml deleted file mode 100644 index 3452f7d4..00000000 --- a/src/deadFlag.ml +++ /dev/null @@ -1,209 +0,0 @@ -(***************************************************************************) -(* *) -(* Copyright (c) 2014-2025 LexiFi SAS. All rights reserved. *) -(* *) -(* This source code is licensed under the MIT License *) -(* found in the LICENSE file at the root of this source tree *) -(* *) -(***************************************************************************) - -type threshold = {exceptions: int; percentage: float; optional: [`Percent | `Both]} - - -type opt = {print: bool; call_sites: bool; threshold: threshold} -let opta = ref - { - print = false; - call_sites = false; - threshold = - { - exceptions = 0; - percentage = 1.; - optional = `Percent - }; - } -let optn = ref - { - print = false; - call_sites = false; - threshold = - { - exceptions = 0; - percentage = 1.; - optional = `Percent - }; - } - - -let update_opt opt s = - let threshold s = - let len = String.length s in - if len > 5 && String.sub s 0 5 = "both:" then begin - let limits = String.sub s 5 (String.length s - 5) in - let thr = - let rec loop s pos len = - if len = String.length s then s - else if s.[pos] = ',' then String.sub s (pos - len) len - else loop s (pos + 1) (len + 1) - in loop limits 0 0 - in - let pos = String.length thr + 1 in - let pct = String.sub limits pos (String.length limits - pos) in - opt := {!opt with threshold={!opt.threshold with optional = `Both}}; - let thr = String.trim thr in - let pct = String.trim pct in - try - opt := {!opt with threshold = {!opt.threshold with exceptions = int_of_string thr}}; - opt := {!opt with threshold = {!opt.threshold with percentage = float_of_string pct}} - with Failure _ -> raise (Arg.Bad ("-Ox: wrong arguments: " ^ limits)) - end - else if len > 8 && String.sub s 0 8 = "percent:" then - let pct = String.sub s 8 (String.length s - 8) |> String.trim in - try opt := {!opt with threshold={!opt.threshold with percentage = float_of_string pct}} - with Failure _ -> raise (Arg.Bad ("-Ox: wrong argument: " ^ pct)) - else raise (Arg.Bad ("-Ox: unknown option " ^ s)) - in - match s with - | "all" -> opt := {!opt with print = true} - | "nothing" -> opt := {!opt with print = false} - | s -> - opt := {!opt with print = true}; - let s = - if String.length s > 6 && String.sub s 0 6 = "calls:" then begin - opt := {!opt with call_sites = true}; - String.sub s 6 (String.length s - 6) - end - else s - in - threshold s; - if !opt.threshold.exceptions < 0 then - raise (Arg.Bad ("-Ox: number of exceptions must be >= 0")) - else if !opt.threshold.percentage > 1. || !opt.threshold.percentage < 0. then - raise (Arg.Bad ("-Ox: percentage must be >= 0.0 and <= 1.0")) - - -type style = {opt_arg: bool; unit_pat: bool; seq: bool; binding: bool} -let style = ref - { - opt_arg = false; - unit_pat = false; - seq = false; - binding = false; - } - -let update_style s = - let rec aux = function - | (b, "opt")::l -> style := {!style with opt_arg = b}; - aux l - | (b, "unit")::l -> style := {!style with unit_pat = b}; - aux l - | (b, "seq")::l -> style := {!style with seq = b}; - aux l - | (b, "bind")::l -> style := {!style with binding = b}; - aux l - | (b, "all")::l -> style := {unit_pat = b; opt_arg = b; seq = b; binding = b}; - aux l - | (_, "")::l -> aux l - | (_, s)::_ -> raise (Arg.Bad ("-S: unknown option: " ^ s)) - | [] -> () - in - let list_of_opt str = - try - let rec split acc pos len = - if str.[pos] <> '+' && str.[pos] <> '-' then - split acc (pos - 1) (len + 1) - else let acc = (str.[pos] = '+', String.trim (String.sub str (pos + 1) len)) :: acc in - if pos > 0 then split acc (pos - 1) 0 - else acc - in split [] (String.length str - 1) 0 - with _ -> raise (Arg.Bad ("options' arguments must start with a delimiter (`+' or `-')")) - in - aux (list_of_opt s) - - -type basic = {print: bool; call_sites: bool; threshold: int} -let exported : basic ref = ref - ({ - print = true; - call_sites = false; - threshold = 0 - } : basic) - - -let obj = ref - ({ - print = true; - call_sites = false; - threshold = 0; - } : basic) - - -let typ : basic ref = ref - ({ - print = true; - call_sites = false; - threshold = 0 - } : basic) - - -let update_basic opt (flag : basic ref) = function - | "all" -> flag := {!flag with print = true} - | "nothing" -> flag := {!flag with print = false} - | s -> - flag := {!flag with print = true}; - let threshold = - let len = String.length s in - if len > 6 && String.sub s 0 6 = "calls:" then begin - flag := {!flag with call_sites = true}; - String.sub s 6 (String.length s - 6) - end - else if len > 10 && String.sub s 0 10 = "threshold:" then - String.sub s 10 (String.length s - 10) - else raise (Arg.Bad (opt ^ ": unknown option: " ^ s)) - in - let threshold = String.trim threshold |> int_of_string in - if threshold < 0 then - raise (Arg.Bad (opt ^ ": integer should be >= 0; Got " ^ string_of_int threshold)) - else flag := {!flag with threshold} - - -let verbose = ref false -let set_verbose () = verbose := true - -(* Print name starting with '_' *) -let underscore = ref true -let set_underscore () = underscore := false - -let internal = ref false -let set_internal () = internal := true - - -let normalize_path s = - let rec split_path s = - let open Filename in - if s = current_dir_name || s = dirname s then [s] - else (basename s) :: (split_path (dirname s)) - in - let rec norm_path = function - | [] -> [] - | x :: ((y :: _) as yss) when x = y && x = Filename.current_dir_name -> norm_path yss - | x :: xss -> - if x = Filename.current_dir_name then norm_path xss (* strip leading ./ *) - else - let yss = List.filter (fun x -> x <> Filename.current_dir_name) xss in - x :: yss - in - let rec concat_path = function - | [] -> "" - | x :: xs -> Filename.concat x (concat_path xs) - in - concat_path (norm_path (List.rev (split_path s))) - -let exclude, is_excluded = - let tbl = Hashtbl.create 10 in - let exclude s = Hashtbl.replace tbl (normalize_path s) () in - let is_excluded s = Hashtbl.mem tbl (normalize_path s) in - exclude, is_excluded - - -let directories : string list ref = ref [] diff --git a/src/deadLexiFi.ml b/src/deadLexiFi.ml index 68b48c0e..2fd31876 100644 --- a/src/deadLexiFi.ml +++ b/src/deadLexiFi.ml @@ -118,12 +118,14 @@ let () = DeadLexiFi.prepare_report := (fun decs -> + let state = State.get_current () in + let sections = state.config.sections in List.iter (fun (strin, pos) -> hashtbl_find_list str strin |> List.iter (fun loc -> - if exported DeadFlag.exported loc then + if exported sections.exported_values loc then LocHash.add_set references loc pos ) ) @@ -163,7 +165,7 @@ let () = else get_type s (pos - 1) in List.iter - ( if exported DeadFlag.typ loc then LocHash.add_set references loc + ( if exported ~is_type:true sections.types loc then LocHash.add_set references loc else ignore ) (hashtbl_find_list dyn_used (get_type path (String.length path - 1))) diff --git a/src/deadMod.ml b/src/deadMod.ml index 33a1679d..84742948 100644 --- a/src/deadMod.ml +++ b/src/deadMod.ml @@ -70,9 +70,11 @@ let expr m = match m.mod_desc with let is_obj = String.contains x '#' in let is_type = not is_obj && DeadType.is_type x in let relevant_report_enabled = - if is_obj then !DeadFlag.obj.DeadFlag.print - else if is_type then exported DeadFlag.typ loc - else exported DeadFlag.exported loc + let state = State.get_current () in + let sections = state.config.sections in + if is_obj then Config.must_report_section sections.methods + else if is_type then exported ~is_type sections.types loc + else exported sections.exported_values loc in let value_is_expected_by_modtype = List.mem x l1 || l1 = [] in if value_is_expected_by_modtype && relevant_report_enabled then @@ -85,7 +87,7 @@ let expr m = match m.mod_desc with (******** WRAPPING ********) let expr m = - if [@warning "-44"] - DeadFlag.(!exported.print || !typ.print || !obj.print) then + let state = State.get_current () in + if [@warning "-44"] Config.must_report_main state.config then expr m else () diff --git a/src/deadObj.ml b/src/deadObj.ml index 2c084fca..cc131d07 100644 --- a/src/deadObj.ml +++ b/src/deadObj.ml @@ -474,7 +474,8 @@ let report () = else acc in - report_basic ~folder decs "UNUSED METHODS" !DeadFlag.obj + let state = State.get_current () in + report_basic ~folder decs "UNUSED METHODS" state.config.sections.methods @@ -482,7 +483,10 @@ let report () = let wrap f x = - if !DeadFlag.obj.print then f x else () + let state = State.get_current () in + if Config.must_report_section state.config.sections.methods then + f x + else () let collect_export path u stock ?obj ?cltyp loc = wrap (collect_export path u stock ~obj ~cltyp) loc diff --git a/src/deadType.ml b/src/deadType.ml index 2902b1b3..1a3b99be 100644 --- a/src/deadType.ml +++ b/src/deadType.ml @@ -102,7 +102,7 @@ let collect_references loc exp_loc = (* Look for bad style typing *) let rec check_style t loc = let state = State.get_current() in - if !DeadFlag.style.DeadFlag.opt_arg then + if state.config.sections.style.opt_arg then match get_deep_desc t with | Tarrow (lab, _, t, _) -> begin match lab with @@ -167,13 +167,21 @@ let tstr typ = | _ -> () -let report () = report_basic decs "UNUSED CONSTRUCTORS/RECORD FIELDS" !DeadFlag.typ +let report () = + let state = State.get_current () in + report_basic + decs + "UNUSED CONSTRUCTORS/RECORD FIELDS" + state.config.sections.types (******** WRAPPING ********) let wrap f x = - if DeadFlag.(!typ.print) then f x else () + let state = State.get_current () in + if Config.must_report_section state.config.sections.types then + f x + else () let collect_export path u stock t = wrap (collect_export path u stock) t let tstr typ = wrap tstr typ diff --git a/src/state/state.ml b/src/state/state.ml index 65550e1c..1418a125 100644 --- a/src/state/state.ml +++ b/src/state/state.ml @@ -1,14 +1,17 @@ module File_infos = File_infos -type t = { - file_infos : File_infos.t; -} +type t = + { config : Config.t + ; file_infos : File_infos.t + } -let empty = {file_infos = File_infos.empty} +let init config = + { config + ; file_infos = File_infos.empty + } -let init cmti_file = - let file_infos = File_infos.init cmti_file in - Result.map (fun file_infos -> {file_infos}) file_infos +let update_config config state = + {state with config} let change_file state cmti_file = let file_infos = state.file_infos in @@ -21,12 +24,16 @@ let change_file state cmti_file = Result.ok state else if equal_no_ext file_infos.cmti_file cmti_file then let file_infos = File_infos.change_file file_infos cmti_file in - Result.map (fun file_infos -> {file_infos}) file_infos + Result.map (fun file_infos -> {state with file_infos}) file_infos else - init cmti_file + let file_infos = File_infos.init cmti_file in + Result.map (fun file_infos -> {state with file_infos}) file_infos (** Analysis' state *) -let current = ref empty +let current = ref + { config = Config.default_config + ; file_infos = File_infos.empty + } let get_current () = !current diff --git a/src/state/state.mli b/src/state/state.mli index a313e24d..f9c0bd28 100644 --- a/src/state/state.mli +++ b/src/state/state.mli @@ -2,18 +2,19 @@ module File_infos = File_infos -type t = { - file_infos : File_infos.t; (** Info about the file being analyzed *) -} +type t = + { config : Config.t (** Configuration of the analysis *) + ; file_infos : File_infos.t (** Info about the file being analyzed *) + } -val empty : t (** The empty state *) +val init : Config.t -> t +(** [init config] initial state for an analysis configured by [config] *) -val init : string -> (t, string) result -(** [init cmti_file] initialize a state to analyze [cmti_file]. - See [File_infos.init] for error cases. *) +val update_config : Config.t -> t -> t +(** [update_config config state] changes the analysis configuration *) val change_file : t -> string -> (t, string) result -(** [cahnge_file t cmti_file] prepare the analysis to move on to [cmti_file]. +(** [change_file t cmti_file] prepare the analysis to move on to [cmti_file]. See [File_infos.change_file] for error cases. *) val get_current : unit -> t diff --git a/src/utils.ml b/src/utils.ml index dced0d6f..cf66a239 100644 --- a/src/utils.ml +++ b/src/utils.ml @@ -8,3 +8,4 @@ let remove_pp fn = let unit fn = Filename.remove_extension (Filename.basename fn) +module StringSet = Set.Make(String) diff --git a/src/utils.mli b/src/utils.mli index c99557e9..ce4b30e9 100644 --- a/src/utils.mli +++ b/src/utils.mli @@ -1,3 +1,5 @@ val remove_pp : string -> string val unit : string -> string + +module StringSet : Set.S with type elt = String.t