123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314(* Yoann Padioleau
*
* Copyright (C) 2010, 2012 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.
*)openCommonopenCst_jsmoduleV=Visitor_jsmoduleE=Entity_code(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*
* Reverse-engineering the class structure of a Javascript file.
*
* Javascript has no notion of class (or module); it's an object-based
* language like Self using *prototypes*. Nevertheless people have
* defined different libraries and conventions to define classes on
* top of those objects. Prototype and Jquery are probably the
* most famous ones.
*
* This module tries to reverse-engineer those conventions.
* By understanding the class structure we can then
* provide better completion on entities by knowing for instance that
* some nested functions are actually static methods of a specific class.
*
* In addition to the Prototype library, this module also handles the
* Javelin conventions and the copy_properties() idiom.
* Javelin, aka JX, is a javascript framework developed by Facebook.
* See http://javelin.fbdocs.com/ It adds some functions like
* JX.install and conventions such as using the 'statics', 'members'
* fields to give a class structure to javascript code.
*
* Because the highlighter needs also to recognize those idioms,
* highlight_js.ml now calls this module.
*
* to test:
* $ ./pfff_db_light -batch_mode -readable_db -lang js ~/www/html/intern/js/
* which right now does not generate any warning.
*
*)(*****************************************************************************)(* Main entry point *)(*****************************************************************************)(* Many of the patterns were written with the help of
* ./pfff_misc -dump_js_ml tests/facebook/mini_www/html/js/bar.js
*)letextract_complete_name_of_infoast=letin_class=ref(None:stringoption)in(* we want to differentiate static from non static methods *)letin_statics=reffalseinletin_members=reffalseinlet(h:(Cst_js.tok,Entity_code.entity_kind*string)Hashtbl.t)=Hashtbl.create101inletv=V.mk_visitor{V.default_visitorwith(* recognize the class *)V.kstmt=(fun(k,_)st->matchstwith(* var Foo = { ... } *)|VarsDecl(_i_1,[Left(VarClassic{v_name=(class_name,info_class);v_type=_;v_init=Some((_i_3,(Object(_body_object))))})],_scopt)->(* could restrict to only toplevel first column var declarations ? *)Hashtbl.addhinfo_class(E.Class,spf"Misc.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->(* todo? really in_members ? or in_statics ? *)Common.save_excursionin_memberstrue(fun()->kst))(* var Foo = (function() { ... })() *)|VarsDecl(_i_1,[Left(VarClassic{v_name=(class_name,info_class);v_type=_;v_init=Some((_i_3,(Apply((Paren((_i_4,(Function(_body_func)),_i_11))),(_i_13,[],_i_14)))))})],_scopt)->Hashtbl.addhinfo_class(E.Class,spf"Misc.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_memberstrue(fun()->kst))|_->kst);V.kexpr=(fun(k,_)e->matchewith(* Foo.prototype = { ... } *)|Assign((Period((V((class_name,info_class))),_i_3,("prototype",_i_4))),(A_eq,_i_6),(Object(_body_object)))->Hashtbl.addhinfo_class(E.Class,spf"Prototype.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_memberstrue(fun()->ke))(* Foo = { } *)|Assign((V((class_name,info_class))),(A_eq,_i_56),(Object(_body_object)))->(* could restrict to only toplevel first column var declarations ? *)Hashtbl.addhinfo_class(E.Class,spf"Misc.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_memberstrue(fun()->ke))(*---------------------------------------------------------------------*)(* Javelin *)(*---------------------------------------------------------------------*)(* JX.install('Foo', { ... } ) *)|Apply((Period((V(("JX",_i_1))),_i_3,("install",_i_4))),(_i_6,(Left((L(String((class_name,info_class)))))::Right(_i_9)::_rest_args),_i_13))->Hashtbl.addhinfo_class(E.Class,spf"JX.%s"class_name);(* was just %s before, but then get conflict between for instance
* DOM.setContent and JX.DOM.setContent
*)Common.save_excursionin_class(Some(spf"JX.%s"class_name))(fun()->ke)(* JX.copy(Foo.prototype, { ... }) *)|Apply((Period((V(("JX",_i_1))),_i_3,("copy",_i_4))),(_i_6,[Left((Period((V((class_name,info_class))),_i_9,("prototype",_i_10))));Right(_i_12);Left((Object(_object_body)))],_i_16))->Hashtbl.addhinfo_class(E.Class,spf"JX.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_staticstrue(fun()->ke))(* Foo.mixin('Arbitrer', { ... }) *)|Apply((Period((V((class_name,info_class))),_i_3,("mixin",_i_4))),(_i_6,[Left((L(String((mixin_name,_info_mixin)))));Right(_i_9);Left((Object(_body_object)))],_i_13))->Hashtbl.addhinfo_class(E.Class,spf"%s<%s"class_namemixin_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_staticstrue(fun()->ke))(* FB.subclass('FB.ApiClient', 'FB.Class', { }); *)|Apply((Period((V(("FB",_i_1))),_i_3,("subclass",_i_4))),(_i_6,[Left((L(String((class_name,info_class)))));Right(_i_9);Left((L(String((_parent_class,_i_10)))));Right(_i_12);Left((Object(_body_object)))],_i_16))->Hashtbl.addhinfo_class(E.Class,spf"%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_staticstrue(fun()->ke))(* FB.provide('FBIntern.Cookie', { } ); *)|Apply((Period((V(("FB",_i_19))),_i_21,("provide",_i_22))),(_i_24,[Left((L(String((class_name,info_class)))));Right(_i_27);Left((Object(_body_object)))],_i_31))->Hashtbl.addhinfo_class(E.Class,spf"%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_staticstrue(fun()->ke))(*---------------------------------------------------------------------*)(* copy_properties *)(*---------------------------------------------------------------------*)(* copy_properties(Foo, { ... } ) *)|Apply((V(("copy_properties",_i_16))),(_i_18,[Left((V((class_name,info_class))));Right(_i_21);Left((Object(_body_object)))],_i_25))->Hashtbl.addhinfo_class(E.Class,spf"Copy_properties.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_staticstrue(fun()->ke))(* copy_properties(Foo.prototype, { ... } ) *)|Apply((V(("copy_properties",info_class))),(_i_3,[Left((Period((V((class_name,_i_4))),_i_6,("prototype",_i_7))));Right(_i_9);Left((Object(_body_object)))],_i_13))->Hashtbl.addhinfo_class(E.Class,spf"Copy_properties.%s"class_name);Common.save_excursionin_class(Someclass_name)(fun()->Common.save_excursionin_staticstrue(fun()->ke))|_->ke);(* recognize the methods *)V.kprop=(fun(k,_)e->matchewith(* Javelin specifics ? *)|P_field(PN_String(("statics",_i_20)),_i_21,_body)->Common.save_excursionin_staticstrue(fun()->ke)|P_field(PN_String(("members",_i_20)),_i_21,_body)->Common.save_excursionin_memberstrue(fun()->ke)(* fld: function (...) { ... } *)|P_field(PN_String((method_name,info_method_name)),_i_40,(Function(_)))->letfullname=(match!in_class,!in_statics,!in_memberswith|Someclassname,true,false->spf"%s::%s"classnamemethod_name|Someclassname,false,true->spf"%s->%s"classnamemethod_name|Some_classname,true,true->pr2("WEIRD: both statics and members at "^Parse_info.string_of_infoinfo_method_name);""|Someclassname,false,false->(matchmethod_namewith|"initialize"|"construct"->spf"%s::%s"classnamemethod_name|_->pr2("WEIRD: in class but no statics or members at "^Parse_info.string_of_infoinfo_method_name);"")|None,true,_|None,_,true->pr2("WEIRD: no class but statics or members at "^Parse_info.string_of_infoinfo_method_name);""|None,_,_->(* jsspec use strings for method names *)letraw_str=Parse_info.str_of_infoinfo_method_nameinifraw_str=~"^[']"then()elsebegin(* TODO too many FPs
pr2 ("WEIRD: no class but function field at " ^
Ast.string_of_info info_method_name);
*)end;"")in(* less: if !in_statics then E.StaticMethod else E.RegularMethod *)Hashtbl.addhinfo_method_name(E.Method,fullname)|_->ke);}inv(Programast);h