ソースを参照

@:inheritDoc implementation (#9817)

Aleksandr Kuzmenko 5 年 前
コミット
31ffb70b0b

+ 6 - 0
src-json/meta.json

@@ -559,6 +559,12 @@
 		"doc": "",
 		"platforms": ["cpp"]
 	},
+	{
+		"name": "InheritDoc",
+		"metadata": ":inheritDoc",
+		"doc": "Append documentation from a parent field or class (if used without an argument) or from a specified class or field (if used like @:inheritDoc(pack.Some.field)).",
+		"targets": ["TClass", "TClass", "TEnum", "TAbstract", "TAnyField"]
+	},
 	{
 		"name": "InitPackage",
 		"metadata": ":initPackage",

+ 4 - 2
src/core/ast.ml

@@ -237,7 +237,7 @@ and type_param = {
 
 and doc_block = {
 	doc_own: string option;
-	mutable doc_inherited: (unit -> (string option)) list
+	mutable doc_inherited: string list;
 }
 
 and documentation = doc_block option
@@ -376,7 +376,9 @@ let doc_from_string_opt = Option.map (fun s -> { doc_own = Some s; doc_inherited
 
 let gen_doc_text d =
 	let docs =
-		match d.doc_own with Some s -> [s] | None -> []
+		match d.doc_own with
+		| Some s -> s :: d.doc_inherited
+		| None -> d.doc_inherited
 	in
 	String.concat "\n" docs
 

+ 231 - 0
src/core/inheritDoc.ml

@@ -0,0 +1,231 @@
+open Globals
+open Ast
+open Type
+open Typecore
+
+let expr_to_target e =
+	let rec loop (e,p) =
+		match e with
+		| EConst (Ident s) when s <> "" -> [s]
+		| EField (e,s) -> s :: loop e
+		| _ -> Error.error "Invalid target expression for @:inheritDoc" p
+	in
+	match loop e with
+	| sub_name :: type_name :: pack when not (is_lower_ident type_name) ->
+		(List.rev pack, type_name), Some sub_name
+	| type_name :: pack ->
+		(List.rev pack, type_name), None
+	| [] ->
+		Error.error "Invalid target path for @:inheritDoc" (snd e)
+
+let rec get_constructor c =
+	match c.cl_constructor, c.cl_super with
+	| Some ctor, _ -> Some c, ctor
+	| None, None -> raise Not_found
+	| None, Some (csup,_) -> get_constructor csup
+
+let rec get_class_field c field_name =
+	try
+		let cf =
+			try PMap.find field_name c.cl_fields
+			with Not_found -> PMap.find field_name c.cl_statics
+		in
+		Some c, cf
+	with Not_found ->
+		match c.cl_super with
+		| None -> raise Not_found
+		| Some (csup, _) -> get_class_field csup field_name
+
+let find_type ctx tp allow_no_params =
+	try Typeload.load_instance' ctx tp allow_no_params
+	with _ -> raise Not_found
+
+(**
+	Finds `@:inheritDoc` meta in `meta` and populates `doc_inherited` field of `doc`
+	with found docs.
+*)
+let rec build_doc ctx ?no_args_cb doc meta =
+	let add d =
+		match d with
+		| None -> ()
+		| Some d ->
+			match gen_doc_text d with
+			| "" -> ()
+			| s ->
+				match !doc with
+				| None -> doc := Some { doc_own = None; doc_inherited = [s]; }
+				| Some doc -> doc.doc_inherited <- s :: doc.doc_inherited
+	in
+	List.iter (fun m ->
+		match m with
+		| (Meta.InheritDoc,[],_) ->
+			(match no_args_cb with
+			| Some fn -> fn add
+			| None -> ())
+		| (Meta.InheritDoc,targets,_) ->
+			List.iter (fun t -> add (get_target_doc ctx t)) targets
+		| _ -> ()
+	) meta
+
+(**
+	Populates `doc_inherited` field of `c.cl_doc`
+*)
+and build_class_doc ctx c =
+	(match c.cl_doc with
+	| None | Some { doc_inherited = [] } -> ()
+	| Some d -> d.doc_inherited <- []
+	);
+	let doc = ref c.cl_doc in
+	let no_args_cb add =
+		match c.cl_super with
+		| None -> ()
+		| Some (csup,_) ->
+			build_class_doc ctx csup;
+			add csup.cl_doc
+	in
+	build_doc ctx ~no_args_cb doc c.cl_meta;
+	c.cl_doc <- !doc
+
+(**
+	Populates `doc_inherited` field of `enm.e_doc`
+*)
+and build_enum_doc ctx enm =
+	(match enm.e_doc with
+	| None | Some { doc_inherited = [] } -> ()
+	| Some d -> d.doc_inherited <- []
+	);
+	let doc = ref enm.e_doc in
+	build_doc ctx doc enm.e_meta;
+	enm.e_doc <- !doc
+
+(**
+	Populates `doc_inherited` field of `a.a_doc`
+*)
+and build_abstract_doc ctx a =
+	(match a.a_doc with
+	| None | Some { doc_inherited = [] } -> ()
+	| Some d -> d.doc_inherited <- []
+	);
+	let doc = ref a.a_doc in
+	build_doc ctx doc a.a_meta;
+	a.a_doc <- !doc
+
+(**
+	Populates `doc_inherited` field of `cf.cf_doc`
+*)
+and build_class_field_doc ctx c_opt cf =
+	(match cf.cf_doc with
+	| None | Some { doc_inherited = [] } -> ()
+	| Some d -> d.doc_inherited <- []
+	);
+	let doc = ref cf.cf_doc in
+	let no_args_cb add =
+		match c_opt with
+		| Some { cl_super = Some (csup,_) } ->
+			(try
+				let c_opt, cf_sup =
+					if cf.cf_name = "new" then get_constructor csup
+					else get_class_field csup cf.cf_name
+				in
+				build_class_field_doc ctx c_opt cf_sup;
+				add cf_sup.cf_doc
+			with Not_found -> ())
+		| _ -> ()
+	in
+	build_doc ctx ~no_args_cb doc cf.cf_meta;
+	cf.cf_doc <- !doc
+
+(**
+	Populates `doc_inherited` field of `ef.ef_doc`
+*)
+and build_enum_field_doc ctx ef =
+	(match ef.ef_doc with
+	| None | Some { doc_inherited = [] } -> ()
+	| Some d -> d.doc_inherited <- []
+	);
+	let doc = ref ef.ef_doc in
+	build_doc ctx doc ef.ef_meta;
+	ef.ef_doc <- !doc
+
+(**
+	Collects `Ast.documentation` for a provided `target`
+	The `target` is an AST expr representing a dot path for a type or a field.
+	E.g. `my.pack.MyType` or `my.pack.MyType.field`
+*)
+and get_target_doc ctx e_target =
+	let path,sub = expr_to_target e_target in
+	let resolve_field field_name =
+		let tp =
+			match List.rev (fst path) with
+			| module_name :: pack_rev when not (is_lower_ident module_name) ->
+				mk_type_path ~sub:(snd path) (List.rev pack_rev,module_name)
+			| _ ->
+				mk_type_path path
+		in
+		let t = (find_type ctx (tp,snd e_target) true) in
+		try
+			match follow t with
+			| TInst (c, _) ->
+				let c_opt, cf =
+					if field_name = "new" then get_constructor c
+					else get_class_field c field_name
+				in
+				build_class_field_doc ctx c_opt cf;
+				cf.cf_doc
+			| TAnon a ->
+				let cf = PMap.find field_name a.a_fields in
+				build_class_field_doc ctx None cf;
+				cf.cf_doc
+			| TEnum (enm, _) ->
+				let ef = PMap.find field_name enm.e_constrs in
+				build_enum_field_doc ctx ef;
+				ef.ef_doc
+			| TAbstract ({ a_impl = Some c }, _) ->
+				let c_opt, cf =
+					let field_name =
+						if field_name = "new" then "_new"
+						else field_name
+					in
+					get_class_field c field_name
+				in
+				build_class_field_doc ctx c_opt cf;
+				cf.cf_doc
+			| _ -> raise Not_found
+		with Not_found ->
+			None
+	in
+	let rec resolve_type_t t =
+		match follow t with
+		| TInst (c, _) ->
+			build_class_doc ctx c;
+			c.cl_doc
+		| TAbstract (a, _) ->
+			build_abstract_doc ctx a;
+			a.a_doc
+		| TEnum (enm, _) ->
+			build_enum_doc ctx enm;
+			enm.e_doc
+		| _ -> raise Not_found
+	in
+	let resolve_type () =
+		let tp = mk_type_path path, snd e_target in
+		resolve_type_t (find_type ctx tp true)
+	in
+	let resolve_sub_type sub =
+		let tp = mk_type_path ~sub path, snd e_target in
+		resolve_type_t (find_type ctx tp true)
+	in
+	try
+		match sub with
+		(* type *)
+		| None ->
+			resolve_type()
+		(* field or sub type *)
+		| Some s ->
+			if is_lower_ident s then
+				resolve_field s
+			else
+				(try resolve_sub_type s
+				with Not_found -> resolve_field s)
+	with Not_found ->
+		None

+ 3 - 3
src/core/tType.ml

@@ -252,7 +252,7 @@ and tenum_field = {
 	mutable ef_type : t;
 	ef_pos : pos;
 	ef_name_pos : pos;
-	ef_doc : Ast.documentation;
+	mutable ef_doc : Ast.documentation;
 	ef_index : int;
 	mutable ef_params : type_params;
 	mutable ef_meta : metadata;
@@ -264,7 +264,7 @@ and tenum = {
 	e_pos : pos;
 	e_name_pos : pos;
 	e_private : bool;
-	e_doc : Ast.documentation;
+	mutable e_doc : Ast.documentation;
 	mutable e_meta : metadata;
 	mutable e_params : type_params;
 	mutable e_using : (tclass * pos) list;
@@ -295,7 +295,7 @@ and tabstract = {
 	a_pos : pos;
 	a_name_pos : pos;
 	a_private : bool;
-	a_doc : Ast.documentation;
+	mutable a_doc : Ast.documentation;
 	mutable a_meta : metadata;
 	mutable a_params : type_params;
 	mutable a_using : (tclass * pos) list;

+ 2 - 0
src/typing/typeloadFields.ml

@@ -1459,6 +1459,8 @@ let init_field (ctx,cctx,fctx) f =
 			create_property (ctx,cctx,fctx) c f (get,set,t,eo) p
 	in
 	(if (fctx.is_static || fctx.is_macro && ctx.in_macro) then add_class_field_flag cf CfStatic);
+	if Meta.has Meta.InheritDoc cf.cf_meta then
+		delay ctx PTypeField (fun() -> InheritDoc.build_class_field_doc ctx (Some c) cf);
 	cf
 
 let check_overload ctx f fs =

+ 9 - 2
src/typing/typeloadModule.ml

@@ -701,6 +701,8 @@ let init_module_type ctx context_init (decl,p) =
 		ctx.pass <- PBuildModule;
 		ctx.curclass <- null_class;
 		delay ctx PBuildClass (fun() -> ignore(c.cl_build()));
+		if Meta.has Meta.InheritDoc c.cl_meta then
+				delay ctx PConnectField (fun() -> InheritDoc.build_class_doc ctx c);
 		if (ctx.com.platform = Java || ctx.com.platform = Cs) && not (has_class_flag c CExtern) then
 			delay ctx PTypeField (fun () ->
 				let metas = StrictMeta.check_strict_meta ctx c.cl_meta in
@@ -779,13 +781,16 @@ let init_module_type ctx context_init (decl,p) =
 			fields := PMap.add cf.cf_name cf !fields;
 			incr index;
 			names := (fst c.ec_name) :: !names;
+			if Meta.has Meta.InheritDoc f.ef_meta then
+				delay ctx PConnectField (fun() -> InheritDoc.build_enum_field_doc ctx f);
 		) (!constructs);
 		e.e_names <- List.rev !names;
 		e.e_extern <- e.e_extern;
 		e.e_type.t_params <- e.e_params;
 		e.e_type.t_type <- mk_anon ~fields:!fields (ref (EnumStatics e));
 		if !is_flat then e.e_meta <- (Meta.FlatEnum,[],null_pos) :: e.e_meta;
-
+		if Meta.has Meta.InheritDoc e.e_meta then
+			delay ctx PConnectField (fun() -> InheritDoc.build_enum_doc ctx e);
 		if (ctx.com.platform = Java || ctx.com.platform = Cs) && not e.e_extern then
 			delay ctx PTypeField (fun () ->
 				let metas = StrictMeta.check_strict_meta ctx e.e_meta in
@@ -906,7 +911,9 @@ let init_module_type ctx context_init (decl,p) =
 				a.a_this <- TAbstract(a,List.map snd a.a_params)
 			else
 				error "Abstract is missing underlying type declaration" a.a_pos
-		end
+		end;
+		if Meta.has Meta.InheritDoc a.a_meta then
+			delay ctx PConnectField (fun() -> InheritDoc.build_abstract_doc ctx a);
 	| EStatic _ ->
 		(* nothing to do here as module fields are collected into a special EClass *)
 		()

+ 39 - 0
tests/server/src/cases/display/InheritDoc.hx

@@ -0,0 +1,39 @@
+package cases.display;
+
+class InheritDoc extends DisplayTestCase {
+	/**
+		import InheritDocTypes;
+
+		class Main {
+			static function main() {
+				var c = new Chi{-1-}ld();
+				c.te{-2-}st();
+				Child.tes{-3-}t2();
+			}
+		}
+	**/
+	function test(_) {
+		vfs.putContent("InheritDocTypes.hx", getTemplate("InheritDocTypes.hx"));
+
+		runHaxeJson([], DisplayMethods.Hover, {
+			file: file,
+			offset: offset(1)
+		});
+		var result = parseHover();
+		Assert.equals(' Child class doc \n GrandParent class doc ', result.result.item.args.doc);
+
+		runHaxeJson([], DisplayMethods.Hover, {
+			file: file,
+			offset: offset(2)
+		});
+		var result = parseHover();
+		Assert.equals(' Child field doc \n GrandParent field doc ', result.result.item.args.field.doc);
+
+		runHaxeJson([], DisplayMethods.Hover, {
+			file: file,
+			offset: offset(3)
+		});
+		var result = parseHover();
+		Assert.equals(' Child field 2 doc \n unrelated field doc ', result.result.item.args.field.doc);
+	}
+}

+ 28 - 0
tests/server/test/templates/InheritDocTypes.hx

@@ -0,0 +1,28 @@
+/** GrandParent class doc */
+class GrandParent {
+	public function new() {}
+
+	/** GrandParent field doc **/
+	public function test() {}
+}
+
+@:inheritDoc
+class Parent extends GrandParent {
+	@:inheritDoc override public function test() {}
+}
+
+/** Child class doc */
+@:inheritDoc
+class Child extends Parent {
+	/** Child field doc **/
+	@:inheritDoc override public function test() {}
+	/** Child field 2 doc **/
+	@:inheritDoc(InheritDocTypes.Unrelated.unrelated)
+	static public function test2() {}
+}
+
+/** Unrelated class doc */
+class Unrelated {
+	/** unrelated field doc */
+	static public function unrelated() {}
+}