Add poc files
This commit is contained in:
parent
30da2412e3
commit
fa600b98f7
220 changed files with 45679 additions and 0 deletions
698
h_program-lang/database_code.ml
Normal file
698
h_program-lang/database_code.ml
Normal file
|
|
@ -0,0 +1,698 @@
|
|||
(* Yoann Padioleau
|
||||
*
|
||||
* Copyright (C) 2009, 2010 Facebook
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation, with the
|
||||
* special exception on linking described in file license.txt.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
|
||||
* license.txt for more details.
|
||||
*)
|
||||
open Common
|
||||
|
||||
open Entity_code
|
||||
module J = Json_type
|
||||
module HC = Highlight_code
|
||||
|
||||
(*****************************************************************************)
|
||||
(* Prelude *)
|
||||
(*****************************************************************************)
|
||||
(*
|
||||
* This module provides a generic "database" of semantic information
|
||||
* on a codebase (a la CIA [1]). The goal is to give access to
|
||||
* information computed by a set of global static or dynamic analysis
|
||||
* such as 'what are the number of callers to a certain function', 'what
|
||||
* is the test coverage of a file', etc. This is mainly used by codemap
|
||||
* to give semantic visual feedback on the code. See also layer_code.ml
|
||||
* for complementary semantic information about a codebase.
|
||||
*
|
||||
* update: prolog_code.pl and Prolog may now be the prefered way to
|
||||
* represent a code database, but for codemap it's still good to use
|
||||
* this database.
|
||||
*
|
||||
* Each programming language analysis library usually provides
|
||||
* a more powerful database (e.g. analyze_php/database/database_php.mli)
|
||||
* with more information. Such a database is usually also efficiently stored
|
||||
* on disk via BerkeleyDB. Nevertheless generic tools like
|
||||
* codemap can benefit from a shorter and generic version of this
|
||||
* database. Moreover, when we have codebase with multiple langages
|
||||
* (e.g. PHP and javascript), having a common type can help for some
|
||||
* analysis or visualization.
|
||||
*
|
||||
* Note that by storing this toy database in a JSON format or with Marshall,
|
||||
* this database can also easily be read by multiple
|
||||
* process at the same time (there is currently a few problems with
|
||||
* concurrent access of Berkeley Db data; for instance one database
|
||||
* created by a user can not even be read by another user ...).
|
||||
* This also avoids forcing the user to spend time running all
|
||||
* the global analysis on his own codebase. We can factorize the essential
|
||||
* results of such long computation in a single file.
|
||||
*
|
||||
* An alternative would be to use the TAGS file or information from
|
||||
* cscope. But this would require to implement a reader for those
|
||||
* two formats. Moreover ctags/cscope do just lexical-based analysis
|
||||
* so it's not a good basis and it contains only defition->position
|
||||
* information.
|
||||
*
|
||||
* history:
|
||||
* - started when working for eurosys'06 in patchparse/ in a file called
|
||||
* c_info.ml
|
||||
* - extended for eurosys'08 for coccinelle/ in coccinelle/extra/
|
||||
* and use it to discover some .c .h mapping and generate some crazy
|
||||
* graphs and also to detect drivers splitted in multiple files.
|
||||
* - extended it for aComment in 2008 and 2009, to feed information to some
|
||||
* inter-procedural analysis.
|
||||
* - rewrite it for PHP in Nov 2009
|
||||
* - adapted in Jan 2010 for flib_navigator
|
||||
* - make it generic in Aug 2010 for my code/treemap visualizer
|
||||
* - added comments about Prolog database which may be a better db for
|
||||
* certain use cases.
|
||||
*
|
||||
* history bis:
|
||||
* - Before, I was optimizing stuff by caching the ast in
|
||||
* some xxx_raw files. But there was lots of small raw files;
|
||||
* get lots of ast files and waste space. Also not good for random
|
||||
* access to the asts. So better to use berkeley DB. My experience with
|
||||
* LFS helped me a little as I was already using berkeley DB and glimpse.
|
||||
*
|
||||
* - I was also using glimpse and I tried to accelerate even more coccinelle
|
||||
* to generate some mini C files so that glimpse can directly tell us
|
||||
* the toplevel elements to look for. But this generates lots of
|
||||
* very small mini C files which also waste lots of disk space.
|
||||
*
|
||||
* References:
|
||||
* [1] CIA, the C Information Abstractor
|
||||
*)
|
||||
|
||||
(*****************************************************************************)
|
||||
(* Type *)
|
||||
(*****************************************************************************)
|
||||
|
||||
(* How to store the id of an entity ? A int ? A name and hope few conflicts ?
|
||||
* Using names will increase the size of the db which will slow down
|
||||
* the loading of the database.
|
||||
* So it's better to use an id. Moreover at some point we want to provide
|
||||
* callers/callees navigations and more entities relationships
|
||||
* so we need a real way to reference an entity.
|
||||
*)
|
||||
type entity_id = int
|
||||
|
||||
type entity = {
|
||||
e_kind: entity_kind;
|
||||
|
||||
e_name: string;
|
||||
(* can be empty to save space when e_fullname = e_name *)
|
||||
e_fullname: string;
|
||||
|
||||
e_file: Common.filename;
|
||||
e_pos: Common2.filepos;
|
||||
|
||||
(* Semantic information that can be leverage by a code visualizer.
|
||||
* The fields are set as mutable because usually we compute
|
||||
* the set of all entities in a first phase and then we
|
||||
* do another pass where we adjust numbers of other entity references.
|
||||
*)
|
||||
|
||||
(* todo: could give more importance when used externally not just
|
||||
* from another file but from another directory!
|
||||
* or could refine this int with more information.
|
||||
*)
|
||||
mutable e_number_external_users: int;
|
||||
|
||||
(* Usually the id of a unit test of pleac file.
|
||||
*
|
||||
* Indeed a simple algorithm to compute this list is:
|
||||
* just look at the callers, filter the one in unit test or pleac files,
|
||||
* then for each caller, look at the number of callees, and take
|
||||
* the one with best ratio.
|
||||
*
|
||||
* With references to good examples of use, we can offer
|
||||
* what Perl programmers had for years with their function
|
||||
* documentations.
|
||||
* If there is no examples_of_use then the user can visually
|
||||
* see that some functions should be unit tested :)
|
||||
*)
|
||||
mutable e_good_examples_of_use: entity_id list;
|
||||
|
||||
(* todo? code_rank ? this is more useful for number_internal_users
|
||||
* when we want to know what is the core function in a module,
|
||||
* even when it's called only once, but by a small wrapper that is
|
||||
* itself very often called.
|
||||
*)
|
||||
|
||||
e_properties: property list;
|
||||
}
|
||||
|
||||
(* Note that because we now use indexed entities, you can not
|
||||
* play with.entities as before. For instance merging databases
|
||||
* requires to adjust all the entity_id internal references.
|
||||
*)
|
||||
type database = {
|
||||
|
||||
(* The common root if the database was built with multiple dirs
|
||||
* as an argument. Such a root is mostly useful when displaying
|
||||
* filenames in which case we can strip the root from it
|
||||
* (e.g. in the treemap browser when we mouse over a rectangle).
|
||||
*)
|
||||
root: Common.dirname;
|
||||
|
||||
(* Such list can be used in a search box powered by completion.
|
||||
* The int is for the total number of times this files is
|
||||
* externally referenced. Can be use for instance in the treemap
|
||||
* to artificially augment the size of what is probably a more
|
||||
* "important" file.
|
||||
*)
|
||||
dirs: (Common.filename * int) list;
|
||||
|
||||
(* see also build_top_k_sorted_entities_per_file for dynamically
|
||||
* computed summary information for a file
|
||||
*)
|
||||
files: (Common.filename * int) list;
|
||||
|
||||
(* indexed by entity_id *)
|
||||
entities: entity array;
|
||||
}
|
||||
|
||||
let empty_database () = {
|
||||
root = "";
|
||||
dirs = [];
|
||||
files = [];
|
||||
entities = Array.of_list [];
|
||||
}
|
||||
|
||||
let default_db_name =
|
||||
"PFFF_DB.marshall"
|
||||
|
||||
|
||||
(*****************************************************************************)
|
||||
(* Json *)
|
||||
(*****************************************************************************)
|
||||
|
||||
(*---------------------------------------------------------------------------*)
|
||||
(* json -> X *)
|
||||
(*---------------------------------------------------------------------------*)
|
||||
|
||||
let json_of_filepos x =
|
||||
J.Array [J.Int x.Common2.l; J.Int x.Common2.c]
|
||||
|
||||
let json_of_property x =
|
||||
match x with
|
||||
| ContainDynamicCall -> J.Array [J.String "ContainDynamicCall"]
|
||||
| ContainReflectionCall -> J.Array [J.String "ContainReflectionCall"]
|
||||
| TakeArgNByRef i -> J.Array [J.String "TakeArgNByRef"; J.Int i]
|
||||
| _ -> raise Todo
|
||||
|
||||
let json_of_entity e =
|
||||
J.Object [
|
||||
"k", J.String (string_of_entity_kind e.e_kind);
|
||||
"n", J.String e.e_name;
|
||||
"fn", J.String e.e_fullname;
|
||||
"f", J.String e.e_file;
|
||||
"p", json_of_filepos e.e_pos;
|
||||
(* different from type *)
|
||||
"cnt", J.Int e.e_number_external_users;
|
||||
"u", J.Array (e.e_good_examples_of_use +> List.map (fun id -> J.Int id));
|
||||
"ps", J.Array (e.e_properties +> List.map json_of_property);
|
||||
]
|
||||
|
||||
let json_of_database db =
|
||||
J.Object [
|
||||
"root", J.String db.root;
|
||||
"dirs", J.Array (db.dirs +> List.map (fun (x, i) ->
|
||||
J.Array([J.String x; J.Int i])));
|
||||
"files", J.Array (db.files +> List.map (fun (x, i) ->
|
||||
J.Array([J.String x; J.Int i])));
|
||||
"entities", J.Array (db.entities +>
|
||||
Array.to_list +> List.map json_of_entity);
|
||||
]
|
||||
|
||||
(*---------------------------------------------------------------------------*)
|
||||
(* X -> json *)
|
||||
(*---------------------------------------------------------------------------*)
|
||||
let ids_of_json json =
|
||||
match json with
|
||||
| J.Array xs ->
|
||||
xs +> List.map (function
|
||||
| J.Int id -> id
|
||||
| _ -> failwith "bad json"
|
||||
)
|
||||
| _ -> failwith "bad json"
|
||||
|
||||
let filepos_of_json json =
|
||||
match json with
|
||||
| J.Array [J.Int l; J.Int c] ->
|
||||
{ Common2.l = l; Common2.c = c }
|
||||
| _ -> failwith "Bad json"
|
||||
|
||||
let property_of_json json =
|
||||
match json with
|
||||
| J.Array [J.String "ContainDynamicCall"] -> ContainDynamicCall
|
||||
| J.Array [J.String "ContainReflectionCall"] -> ContainReflectionCall
|
||||
| J.Array [J.String "TakeArgNByRef"; J.Int i] -> TakeArgNByRef i
|
||||
| _ -> failwith "property_of_json: bad json"
|
||||
|
||||
|
||||
let properties_of_json json =
|
||||
match json with
|
||||
| J.Array xs ->
|
||||
xs +> List.map property_of_json
|
||||
| _ -> failwith "Bad json"
|
||||
|
||||
(* Reverse of json_of_entity_info; must follow same convention for the order
|
||||
* of the fields.
|
||||
*)
|
||||
let entity_of_json2 json =
|
||||
match json with
|
||||
| J.Object [
|
||||
"k", J.String e_kind;
|
||||
"n", J.String e_name;
|
||||
"fn", J.String e_fullname;
|
||||
"f", J.String e_file;
|
||||
"p", e_pos;
|
||||
(* different from type *)
|
||||
"cnt", J.Int e_number_external_users;
|
||||
"u", ids;
|
||||
"ps", properties;
|
||||
] -> {
|
||||
e_kind = entity_kind_of_string e_kind;
|
||||
e_name = e_name;
|
||||
e_file = e_file;
|
||||
e_fullname = e_fullname;
|
||||
e_pos = filepos_of_json e_pos;
|
||||
e_number_external_users = e_number_external_users;
|
||||
e_good_examples_of_use = ids_of_json ids;
|
||||
e_properties = properties_of_json properties;
|
||||
}
|
||||
| _ -> failwith "Bad json"
|
||||
|
||||
let entity_of_json a =
|
||||
Common.profile_code "Db.entity_of_json" (fun () ->
|
||||
entity_of_json2 a)
|
||||
|
||||
|
||||
let database_of_json2 json =
|
||||
match json with
|
||||
| J.Object [
|
||||
"root", J.String db_root;
|
||||
"dirs", J.Array db_dirs;
|
||||
"files", J.Array db_files;
|
||||
"entities", J.Array db_entities;
|
||||
] -> {
|
||||
root = db_root;
|
||||
|
||||
dirs = db_dirs +> List.map (fun json ->
|
||||
match json with
|
||||
| J.Array([J.String x; J.Int i]) ->
|
||||
x, i
|
||||
| _ -> failwith "Bad json"
|
||||
);
|
||||
|
||||
files = db_files +> List.map (fun json ->
|
||||
match json with
|
||||
| J.Array([J.String x; J.Int i]) ->
|
||||
x, i
|
||||
| _ -> failwith "Bad json"
|
||||
);
|
||||
entities =
|
||||
db_entities +> List.map entity_of_json +> Array.of_list
|
||||
}
|
||||
|
||||
| _ -> failwith "Bad json"
|
||||
|
||||
let database_of_json json =
|
||||
Common.profile_code "Db.database_of_json" (fun () ->
|
||||
database_of_json2 json
|
||||
)
|
||||
|
||||
(*****************************************************************************)
|
||||
(* Load/Save *)
|
||||
(*****************************************************************************)
|
||||
|
||||
let load_database2 file =
|
||||
pr2 (spf "loading database: %s" file);
|
||||
if File_type.is_json_filename file
|
||||
then
|
||||
(* This code is mostly obsolete. It's more efficient to use Marshall
|
||||
* to store big database. This should be used only when
|
||||
* one wants to have a readable database.
|
||||
*)
|
||||
let json =
|
||||
Common.profile_code "Json_in.load_json" (fun () ->
|
||||
Json_in.load_json file
|
||||
) in
|
||||
database_of_json json
|
||||
else Common2.get_value file
|
||||
|
||||
let load_database file =
|
||||
Common.profile_code "Db.load_db" (fun () -> load_database2 file)
|
||||
|
||||
(* We allow to save in JSON format because it may be useful to let
|
||||
* the user edit read the generated data.
|
||||
*
|
||||
* less: could use the more efficient json pretty printer, but really
|
||||
* marshall is probably better. Only biniou could be a valid alternative.
|
||||
*)
|
||||
let save_database database file =
|
||||
if File_type.is_json_filename file
|
||||
then
|
||||
database +> json_of_database
|
||||
+> Json_io.string_of_json ~compact:false ~recursive:false ~allow_nan:true
|
||||
+> Common.write_file ~file
|
||||
else Common2.write_value database file
|
||||
|
||||
|
||||
(*****************************************************************************)
|
||||
(* Entities categories *)
|
||||
(*****************************************************************************)
|
||||
|
||||
(* coupling: if you add a new kind of entity, then
|
||||
* don't forget to modify size_font_multiplier_of_categ in code_map/
|
||||
*
|
||||
* How sure this list is exhaustive ? C-c for usedef2
|
||||
*)
|
||||
let entity_kind_of_highlight_category_def categ =
|
||||
match categ with
|
||||
| HC.Entity (kind, HC.Def2 _) -> Some kind
|
||||
|
||||
| HC.FunctionDecl _ -> Some Prototype
|
||||
| HC.StaticMethod (HC.Def2 _) -> Some Method
|
||||
| HC.StructName (HC.Def) -> Some Type
|
||||
|
||||
(* todo: what about other Def ? like Label, Parameter, etc ? *)
|
||||
| _ -> None
|
||||
|
||||
let is_entity_def_category categ =
|
||||
entity_kind_of_highlight_category_def categ <> None
|
||||
|
||||
(* less: merge with other function? *)
|
||||
let entity_kind_of_highlight_category_use categ =
|
||||
match categ with
|
||||
| HC.Entity (kind, HC.Use2 _) -> Some kind
|
||||
| HC.FunctionDecl _ -> Some Function
|
||||
| HC.StaticMethod (HC.Use2 _) -> Some Method
|
||||
| HC.StructName HC.Use -> Some Class
|
||||
| _ -> None
|
||||
|
||||
|
||||
let matching_def_short_kind_kind short_kind kind =
|
||||
(match short_kind, kind with
|
||||
(* Struct/Union are generated as Type for now in graph_code_clang.ml *)
|
||||
| Class, Type -> true
|
||||
| Global, GlobalExtern -> true
|
||||
| Function, Prototype -> true
|
||||
| a, b -> a =*= b
|
||||
)
|
||||
|
||||
(* See the code of the different highlight_code_xxx.ml to
|
||||
* know the different possible pairs.
|
||||
* todo: merge with other functions too?
|
||||
*)
|
||||
let matching_use_categ_kind categ kind =
|
||||
match kind, categ with
|
||||
| kind1, HC.Entity (kind2, _) when kind1 =*= kind2 -> true
|
||||
|
||||
| Prototype, HC.Entity (Function, _)
|
||||
| Constructor, HC.ConstructorMatch _
|
||||
| GlobalExtern, HC.Entity (Global, _)
|
||||
| Method, HC.StaticMethod _
|
||||
| ClassConstant, HC.Entity (Constant, _)
|
||||
|
||||
(* tofix at some point, wrong tokenizer *)
|
||||
| Constant, HC.Local _
|
||||
| Global, HC.Local _
|
||||
| Function, HC.Local _
|
||||
| Constructor, HC.Entity (Global, _)
|
||||
| Function, HC.Builtin
|
||||
| Function, HC.BuiltinCommentColor
|
||||
| Function, HC.BuiltinBoolean
|
||||
(* because what looks like a constant is actually a partially applied func *)
|
||||
| Function, HC.Entity (Constant, _)
|
||||
|
||||
(* function pointers in structure initialized (poor's man oo in C) *)
|
||||
| Function, HC.Entity (Global, _)
|
||||
(* function calls to pointer function via direct syntax *)
|
||||
| GlobalExtern, HC.Entity (Function, _)
|
||||
|
||||
| Global, HC.UseOfRef
|
||||
| Field, HC.UseOfRef
|
||||
-> true
|
||||
|
||||
| _ -> false
|
||||
|
||||
|
||||
|
||||
(* In database_light_xxx we sometimes need, given a 'use', to increment
|
||||
* the e_number_external_users counter of an entity. Nevertheless
|
||||
* multiple entities may have the same name in which case looking
|
||||
* for an entity in the environment will return multiple
|
||||
* entities of different kinds. Here we filter back the
|
||||
* non valid entities.
|
||||
*)
|
||||
let entity_and_highlight_category_correpondance entity categ =
|
||||
let entity_kind_use =
|
||||
Common2.some (entity_kind_of_highlight_category_use categ) in
|
||||
entity.e_kind = entity_kind_use
|
||||
|
||||
(*****************************************************************************)
|
||||
(* Misc *)
|
||||
(*****************************************************************************)
|
||||
|
||||
(* When we compute the light database for a language we usually start
|
||||
* by calling a function to get the set of files in this language
|
||||
* (e.g. Lib_parsing_ml.find_all_ml_files) and then "infer"
|
||||
* the set of directories used by those files by just calling dirname
|
||||
* on them. Nevertheless in the search box of the visualizer we
|
||||
* want to propose for instance flib/herald even if there is no
|
||||
* php file in flib/herald but files in flib/herald/lib/foo.php.
|
||||
* Having flib/herald/lib is not enough. Enter alldirs_and_parent_dirs_of_dirs
|
||||
* which will compute all the directories.
|
||||
*
|
||||
* It's a kind of 'find -type d' but reversed, using a set of complete dirs
|
||||
* as the starting point. In fact we could define a
|
||||
* Common.dirs_of_dirs but then directory without any interesting files
|
||||
* would be listed.
|
||||
*)
|
||||
let alldirs_and_parent_dirs_of_relative_dirs dirs =
|
||||
dirs
|
||||
+> List.map Common2.inits_of_relative_dir
|
||||
+> List.flatten +> Common2.uniq_eff
|
||||
|
||||
|
||||
let merge_databases db1 db2 =
|
||||
(* assert same root ?then can just add the fields *)
|
||||
if db1.root <> db2.root
|
||||
then begin
|
||||
pr2 (spf "merge_database: the root differs, %s != %s"
|
||||
db1.root db2.root);
|
||||
if not (Common2.y_or_no "Continue ?")
|
||||
then failwith "ok we stop";
|
||||
end;
|
||||
|
||||
(* entities now contain references to other entities through
|
||||
* the index to the entities array. So concatenating 2 array
|
||||
* entities requires care.
|
||||
*)
|
||||
let length_entities1 = Array.length db1.entities in
|
||||
|
||||
let db2_entities = db2.entities in
|
||||
let db2_entities_adjusted =
|
||||
db2_entities +> Array.map (fun e ->
|
||||
{ e with
|
||||
e_good_examples_of_use =
|
||||
e.e_good_examples_of_use
|
||||
+> List.map (fun id -> id + length_entities1);
|
||||
}
|
||||
)
|
||||
in
|
||||
|
||||
{
|
||||
root = db1.root;
|
||||
dirs = (db1.dirs @ db2.dirs)
|
||||
+> Common.group_assoc_bykey_eff
|
||||
+> List.map (fun (file, xs) ->
|
||||
file, Common2.sum xs
|
||||
);
|
||||
files = db1.files @ db2.files; (* should ensure exclusive ? *)
|
||||
entities = Array.append db1.entities db2_entities_adjusted;
|
||||
}
|
||||
|
||||
|
||||
let build_top_k_sorted_entities_per_file2 ~k xs =
|
||||
xs
|
||||
+> Array.to_list
|
||||
+> List.map (fun e -> e.e_file, e)
|
||||
+> Common.group_assoc_bykey_eff
|
||||
+> List.map (fun (file, xs) ->
|
||||
file, (xs +> List.sort (fun e1 e2 ->
|
||||
(* high first *)
|
||||
compare e2.e_number_external_users e1.e_number_external_users
|
||||
) +> Common.take_safe k
|
||||
)
|
||||
) +> Common.hash_of_list
|
||||
|
||||
let build_top_k_sorted_entities_per_file ~k xs =
|
||||
Common.profile_code "Db.build_sorted_entities" (fun () ->
|
||||
build_top_k_sorted_entities_per_file2 ~k xs
|
||||
)
|
||||
|
||||
|
||||
let mk_dir_entity dir n = {
|
||||
e_name = Common2.basename dir ^ "/";
|
||||
e_fullname = "";
|
||||
e_file = dir;
|
||||
e_pos = { Common2.l = 1; c = 0 };
|
||||
e_kind = Dir;
|
||||
e_number_external_users = n;
|
||||
e_good_examples_of_use = [];
|
||||
e_properties = [];
|
||||
}
|
||||
let mk_file_entity file n = {
|
||||
e_name = Common2.basename file;
|
||||
e_fullname = "";
|
||||
e_file = file;
|
||||
e_pos = { Common2.l = 1; c = 0 };
|
||||
e_kind = File;
|
||||
e_number_external_users = n;
|
||||
e_good_examples_of_use = [];
|
||||
e_properties = [];
|
||||
}
|
||||
|
||||
let mk_multi_dirs_entity name dirs_entities =
|
||||
let dirs_fullnames = dirs_entities +> List.map (fun e -> e.e_file) in
|
||||
|
||||
{
|
||||
e_name = name ^ "//";
|
||||
(* hack *)
|
||||
e_fullname = "";
|
||||
|
||||
(* hack *)
|
||||
e_file = Common.join "|" dirs_fullnames;
|
||||
|
||||
e_pos = { Common2.l = 1; c = 0 };
|
||||
e_kind = MultiDirs;
|
||||
e_number_external_users =
|
||||
(* todo? *)
|
||||
(List.length dirs_fullnames);
|
||||
e_good_examples_of_use = [];
|
||||
e_properties = [];
|
||||
}
|
||||
|
||||
let multi_dirs_entities_of_dirs es =
|
||||
let h = Hashtbl.create 101 in
|
||||
es +> List.iter (fun e ->
|
||||
Hashtbl.add h e.e_name e
|
||||
);
|
||||
let keys = Common2.hkeys h in
|
||||
keys +> Common.map_filter (fun k ->
|
||||
let vs = Hashtbl.find_all h k in
|
||||
if List.length vs > 1
|
||||
then Some (mk_multi_dirs_entity k vs)
|
||||
else None
|
||||
)
|
||||
|
||||
let files_and_dirs_database_from_files ~root files =
|
||||
|
||||
(* quite similar to what we first do in a database_light_xxx.ml *)
|
||||
let dirs = files +> List.map Filename.dirname +> Common2.uniq_eff in
|
||||
let dirs = dirs +> List.map (fun s -> Common.readable ~root s) in
|
||||
let dirs = alldirs_and_parent_dirs_of_relative_dirs dirs in
|
||||
|
||||
{ root = root;
|
||||
dirs = dirs +> List.map (fun d -> d, 0); (* TODO *)
|
||||
files = files +> List.map (fun f -> Common.readable ~root f, 0); (* TODO *)
|
||||
entities = [| |];
|
||||
}
|
||||
|
||||
|
||||
let files_and_dirs_and_sorted_entities_for_completion2
|
||||
~threshold_too_many_entities
|
||||
db
|
||||
=
|
||||
let nb_entities = Array.length db.entities in
|
||||
|
||||
let dirs =
|
||||
db.dirs +> List.map (fun (dir, n) -> mk_dir_entity dir n)
|
||||
in
|
||||
let files =
|
||||
db.files +> List.map (fun (file, n) -> mk_file_entity file n)
|
||||
in
|
||||
let multidirs = multi_dirs_entities_of_dirs dirs in
|
||||
|
||||
let xs =
|
||||
multidirs @ dirs @ files @
|
||||
(if nb_entities > threshold_too_many_entities
|
||||
then begin
|
||||
pr2 "Too many entities. Completion just for filenames";
|
||||
[]
|
||||
end else
|
||||
(db.entities +> Array.to_list +> List.map (fun e ->
|
||||
(* we used to return 2 entities per entity by having
|
||||
* both an entity with the short name and one with the long
|
||||
* name, but now that we do a suffix search, no need
|
||||
* to keep the short one
|
||||
*)
|
||||
if e.e_fullname = ""
|
||||
then e
|
||||
else { e with e_name = e.e_fullname }
|
||||
)
|
||||
)
|
||||
)
|
||||
in
|
||||
|
||||
(* note: return first the dirs and files so that when offer
|
||||
* completion the dirs and files will be proposed first
|
||||
* (could also enforce this rule when building the gtk completion model).
|
||||
*)
|
||||
xs +> List.map (fun e ->
|
||||
(match e.e_kind with
|
||||
| MultiDirs -> 100
|
||||
| Dir -> 40
|
||||
| File -> 20
|
||||
| _ -> e.e_number_external_users
|
||||
), e
|
||||
) +> Common.sort_by_key_highfirst
|
||||
+> List.map snd
|
||||
|
||||
|
||||
let files_and_dirs_and_sorted_entities_for_completion
|
||||
~threshold_too_many_entities a =
|
||||
Common.profile_code "Db.sorted_entities" (fun () ->
|
||||
files_and_dirs_and_sorted_entities_for_completion2
|
||||
~threshold_too_many_entities a)
|
||||
|
||||
|
||||
|
||||
(* The e_number_external_users count is not always very accurate for methods
|
||||
* when we do very trivial class/methods analysis for some languages.
|
||||
* This helper function can compensate back this approximation.
|
||||
*)
|
||||
let adjust_method_or_field_external_users ~verbose entities =
|
||||
(* phase1: collect all method counts *)
|
||||
let h_method_def_count = Common2.hash_with_default (fun () -> 0) in
|
||||
|
||||
entities +> Array.iter (fun e ->
|
||||
match e.e_kind with
|
||||
| Method | Field ->
|
||||
let k = e.e_name in
|
||||
h_method_def_count#update k (Common2.add1)
|
||||
| _ -> ()
|
||||
);
|
||||
|
||||
(* phase2: adjust *)
|
||||
entities +> Array.iter (fun e ->
|
||||
match e.e_kind with
|
||||
| Method | Field ->
|
||||
let k = e.e_name in
|
||||
let nb_defs = h_method_def_count#assoc k in
|
||||
if nb_defs > 1 && verbose
|
||||
then pr2 ("Adjusting: " ^ e.e_fullname);
|
||||
|
||||
let orig_number = e.e_number_external_users in
|
||||
e.e_number_external_users <- orig_number / nb_defs;
|
||||
| _ -> ()
|
||||
);
|
||||
()
|
||||
Loading…
Add table
Add a link
Reference in a new issue