Skip to content

Commit ea942ff

Browse files
committed
mcp docgen wip
1 parent 2a98814 commit ea942ff

File tree

3 files changed

+358
-8
lines changed

3 files changed

+358
-8
lines changed

analysis/src/DocGen.ml

Lines changed: 285 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,290 @@ module JsonOutput = struct
324324
]
325325
end
326326

327-
module MdOutput = struct end
327+
module MdOutput = struct
328+
let stringifyDocstrings docstrings =
329+
match docstrings with
330+
| [] -> ""
331+
| docstrings ->
332+
docstrings |> List.map String.trim
333+
|> List.filter (fun s -> s <> "")
334+
|> String.concat "\n\n"
335+
336+
let stringifyFieldDoc (fieldDoc : fieldDoc) =
337+
let buffer = Buffer.create 256 in
338+
Buffer.add_string buffer
339+
(Printf.sprintf "- **FIELD:** `%s`\n" fieldDoc.fieldName);
340+
Buffer.add_string buffer
341+
(Printf.sprintf " - **TYPE:** `%s`\n" fieldDoc.signature);
342+
Buffer.add_string buffer
343+
(Printf.sprintf " - **OPTIONAL:** %s\n"
344+
(string_of_bool fieldDoc.optional));
345+
(match fieldDoc.deprecated with
346+
| Some d ->
347+
Buffer.add_string buffer (Printf.sprintf " - **DEPRECATED:** %s\n" d)
348+
| None -> ());
349+
(match stringifyDocstrings fieldDoc.docstrings with
350+
| "" -> ()
351+
| docs ->
352+
Buffer.add_string buffer (Printf.sprintf " - **DESCRIPTION:** %s\n" docs));
353+
Buffer.contents buffer
354+
355+
let stringifyConstructorDoc (constructorDoc : constructorDoc) =
356+
let buffer = Buffer.create 256 in
357+
Buffer.add_string buffer
358+
(Printf.sprintf "- **CONSTRUCTOR:** `%s`\n" constructorDoc.constructorName);
359+
Buffer.add_string buffer
360+
(Printf.sprintf " - **SIGNATURE:** `%s`\n" constructorDoc.signature);
361+
(match constructorDoc.deprecated with
362+
| Some d ->
363+
Buffer.add_string buffer (Printf.sprintf " - **DEPRECATED:** %s\n" d)
364+
| None -> ());
365+
(match stringifyDocstrings constructorDoc.docstrings with
366+
| "" -> ()
367+
| docs ->
368+
Buffer.add_string buffer (Printf.sprintf " - **DESCRIPTION:** %s\n" docs));
369+
(match constructorDoc.items with
370+
| None -> ()
371+
| Some (InlineRecord {fieldDocs}) ->
372+
Buffer.add_string buffer " - **INLINE_RECORD_FIELDS:**\n";
373+
fieldDocs
374+
|> List.iter (fun field ->
375+
let field_lines =
376+
stringifyFieldDoc field |> String.split_on_char '\n'
377+
in
378+
field_lines
379+
|> List.iter (fun line ->
380+
if String.trim line <> "" then
381+
Buffer.add_string buffer (" " ^ line ^ "\n"))));
382+
Buffer.contents buffer
383+
384+
let stringifyDetail (detail : docItemDetail) =
385+
match detail with
386+
| Record {fieldDocs} ->
387+
let buffer = Buffer.create 512 in
388+
Buffer.add_string buffer "\n**RECORD_FIELDS:**\n";
389+
fieldDocs
390+
|> List.iter (fun field ->
391+
Buffer.add_string buffer (stringifyFieldDoc field ^ "\n"));
392+
Buffer.contents buffer
393+
| Variant {constructorDocs} ->
394+
let buffer = Buffer.create 512 in
395+
Buffer.add_string buffer "\n**VARIANT_CONSTRUCTORS:**\n";
396+
constructorDocs
397+
|> List.iter (fun constructor ->
398+
Buffer.add_string buffer
399+
(stringifyConstructorDoc constructor ^ "\n"));
400+
Buffer.contents buffer
401+
| Signature {parameters = _; returnType = _} ->
402+
(* For function signatures, we could add parameter details, but for now keep it simple *)
403+
""
404+
405+
let stringifySource (source : source) =
406+
Printf.sprintf "**SOURCE:** %s:%d:%d" source.filepath source.line source.col
407+
408+
let rec stringifyDocItem ?(depth = 2) (item : docItem) =
409+
let heading = String.make depth '#' ^ " " in
410+
411+
match item with
412+
| Value {name; docstring; signature; deprecated; detail; source} ->
413+
let buffer = Buffer.create 1024 in
414+
Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading name);
415+
Buffer.add_string buffer "**KIND:** VALUE\n\n";
416+
Buffer.add_string buffer
417+
(Printf.sprintf "**SIGNATURE:** `%s`\n\n" signature);
418+
Buffer.add_string buffer
419+
(Printf.sprintf "%s\n\n" (stringifySource source));
420+
(match deprecated with
421+
| Some d ->
422+
Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d)
423+
| None -> ());
424+
(match stringifyDocstrings docstring with
425+
| "" -> ()
426+
| docs ->
427+
Buffer.add_string buffer
428+
(Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs));
429+
(match detail with
430+
| None -> ()
431+
| Some d -> Buffer.add_string buffer (stringifyDetail d ^ "\n"));
432+
Buffer.contents buffer
433+
| Type {name; docstring; signature; deprecated; detail; source} ->
434+
let buffer = Buffer.create 1024 in
435+
Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading name);
436+
Buffer.add_string buffer "**KIND:** TYPE\n\n";
437+
Buffer.add_string buffer
438+
(Printf.sprintf "**SIGNATURE:** `%s`\n\n" signature);
439+
Buffer.add_string buffer
440+
(Printf.sprintf "%s\n\n" (stringifySource source));
441+
(match deprecated with
442+
| Some d ->
443+
Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d)
444+
| None -> ());
445+
(match stringifyDocstrings docstring with
446+
| "" -> ()
447+
| docs ->
448+
Buffer.add_string buffer
449+
(Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs));
450+
(match detail with
451+
| None -> ()
452+
| Some d -> Buffer.add_string buffer (stringifyDetail d ^ "\n"));
453+
Buffer.contents buffer
454+
| Module m ->
455+
let buffer = Buffer.create 1024 in
456+
Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading m.name);
457+
Buffer.add_string buffer "**KIND:** MODULE\n\n";
458+
Buffer.add_string buffer
459+
(Printf.sprintf "%s\n\n" (stringifySource m.source));
460+
(match m.moduletypeid with
461+
| Some id ->
462+
Buffer.add_string buffer
463+
(Printf.sprintf "**MODULE_TYPE_ID:** %s\n\n" id)
464+
| None -> ());
465+
(match m.deprecated with
466+
| Some d ->
467+
Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d)
468+
| None -> ());
469+
(match stringifyDocstrings m.docstring with
470+
| "" -> ()
471+
| docs ->
472+
Buffer.add_string buffer
473+
(Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs));
474+
(match m.items with
475+
| [] -> ()
476+
| items ->
477+
Buffer.add_string buffer "**MODULE_CONTENTS:**\n\n";
478+
items
479+
|> List.iter (fun item ->
480+
Buffer.add_string buffer
481+
(stringifyDocItem ~depth:(depth + 1) item ^ "\n")));
482+
Buffer.contents buffer
483+
| ModuleType m ->
484+
let buffer = Buffer.create 1024 in
485+
Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading m.name);
486+
Buffer.add_string buffer "**KIND:** MODULE_TYPE\n\n";
487+
Buffer.add_string buffer
488+
(Printf.sprintf "%s\n\n" (stringifySource m.source));
489+
(match m.deprecated with
490+
| Some d ->
491+
Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d)
492+
| None -> ());
493+
(match stringifyDocstrings m.docstring with
494+
| "" -> ()
495+
| docs ->
496+
Buffer.add_string buffer
497+
(Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs));
498+
(match m.items with
499+
| [] -> ()
500+
| items ->
501+
Buffer.add_string buffer "**MODULE_TYPE_CONTENTS:**\n\n";
502+
items
503+
|> List.iter (fun item ->
504+
Buffer.add_string buffer
505+
(stringifyDocItem ~depth:(depth + 1) item ^ "\n")));
506+
Buffer.contents buffer
507+
| ModuleAlias m ->
508+
let buffer = Buffer.create 1024 in
509+
Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading m.name);
510+
Buffer.add_string buffer "**KIND:** MODULE_ALIAS\n\n";
511+
Buffer.add_string buffer
512+
(Printf.sprintf "%s\n\n" (stringifySource m.source));
513+
(match stringifyDocstrings m.docstring with
514+
| "" -> ()
515+
| docs ->
516+
Buffer.add_string buffer
517+
(Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs));
518+
(match m.items with
519+
| [] -> ()
520+
| items ->
521+
Buffer.add_string buffer "**ALIASED_MODULE_CONTENTS:**\n\n";
522+
items
523+
|> List.iter (fun item ->
524+
Buffer.add_string buffer
525+
(stringifyDocItem ~depth:(depth + 1) item ^ "\n")));
526+
Buffer.contents buffer
527+
528+
let stringifyDocsForModule (docs : docsForModule) =
529+
let buffer = Buffer.create 4096 in
530+
531+
(* Header with metadata *)
532+
Buffer.add_string buffer (Printf.sprintf "# %s\n\n" docs.name);
533+
Buffer.add_string buffer "**KIND:** MODULE\n\n";
534+
Buffer.add_string buffer
535+
(Printf.sprintf "%s\n\n" (stringifySource docs.source));
536+
537+
(match docs.deprecated with
538+
| Some d ->
539+
Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d)
540+
| None -> ());
541+
542+
(match stringifyDocstrings docs.docstring with
543+
| "" -> ()
544+
| docs_str ->
545+
Buffer.add_string buffer
546+
(Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs_str));
547+
548+
(* Group items by type for better organization *)
549+
let values, types, modules, module_types, module_aliases =
550+
docs.items
551+
|> List.fold_left
552+
(fun (vals, typs, mods, mod_typs, mod_aliases) item ->
553+
match item with
554+
| Value _ -> (item :: vals, typs, mods, mod_typs, mod_aliases)
555+
| Type _ -> (vals, item :: typs, mods, mod_typs, mod_aliases)
556+
| Module _ -> (vals, typs, item :: mods, mod_typs, mod_aliases)
557+
| ModuleType _ -> (vals, typs, mods, item :: mod_typs, mod_aliases)
558+
| ModuleAlias _ -> (vals, typs, mods, mod_typs, item :: mod_aliases))
559+
([], [], [], [], [])
560+
in
561+
562+
let values = List.rev values in
563+
let types = List.rev types in
564+
let modules = List.rev modules in
565+
let module_types = List.rev module_types in
566+
let module_aliases = List.rev module_aliases in
567+
568+
(* Content sections with clear headers *)
569+
(match values with
570+
| [] -> ()
571+
| _ ->
572+
Buffer.add_string buffer "## VALUES\n\n";
573+
values
574+
|> List.iter (fun item ->
575+
Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n")));
576+
577+
(match types with
578+
| [] -> ()
579+
| _ ->
580+
Buffer.add_string buffer "## TYPES\n\n";
581+
types
582+
|> List.iter (fun item ->
583+
Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n")));
584+
585+
(match modules with
586+
| [] -> ()
587+
| _ ->
588+
Buffer.add_string buffer "## MODULES\n\n";
589+
modules
590+
|> List.iter (fun item ->
591+
Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n")));
592+
593+
(match module_types with
594+
| [] -> ()
595+
| _ ->
596+
Buffer.add_string buffer "## MODULE_TYPES\n\n";
597+
module_types
598+
|> List.iter (fun item ->
599+
Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n")));
600+
601+
(match module_aliases with
602+
| [] -> ()
603+
| _ ->
604+
Buffer.add_string buffer "## MODULE_ALIASES\n\n";
605+
module_aliases
606+
|> List.iter (fun item ->
607+
Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n")));
608+
609+
Buffer.contents buffer
610+
end
328611

329612
let fieldToFieldDoc (field : SharedTypes.field) : fieldDoc =
330613
{
@@ -655,5 +938,5 @@ let extractDocsToJson ~entryPointFile ~debug =
655938

656939
let extractDocsToMd ~entryPointFile ~debug =
657940
match extractDocs ~entryPointFile ~debug with
658-
| Ok (_docs, _env) -> Ok "wip"
941+
| Ok (docs, _env) -> Ok (MdOutput.stringifyDocsForModule docs)
659942
| Error e -> Error e

analysis/src/Mcp.ml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,14 @@ module Docs = struct
235235
let docs ~called_from ~(typ : docsType) ~identifier =
236236
let result =
237237
match Cmt.loadFullCmtFromPath ~path:called_from with
238-
| None -> None
239-
| Some full -> None
238+
| None -> Error "Could not load cmt file"
239+
| Some _full -> (
240+
match typ with
241+
| ProjectFile ->
242+
DocGen.extractDocsToMd ~entryPointFile:identifier ~debug:false
243+
| Library -> Ok "TODO")
240244
in
241245
match result with
242-
| None -> "No result."
243-
| Some s -> s
246+
| Error e -> "Error: " ^ e
247+
| Ok s -> s
244248
end

tests/analysis_tests/tests/src/expected/MCP.res.txt

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,71 @@ type t<'t> = {next: 't => unit}
3939
</type>
4040

4141
MCP docs for project file
42-
No result.
42+
# MCPDocs
43+
44+
**KIND:** MODULE
45+
46+
**SOURCE:** src/MCPDocs.res:1:1
47+
48+
## TYPES
49+
50+
### x
51+
52+
**KIND:** TYPE
53+
54+
**SIGNATURE:** `type x = {test: bool}`
55+
56+
**SOURCE:** src/MCPDocs.res:1:1
57+
58+
59+
**RECORD_FIELDS:**
60+
- **FIELD:** `test`
61+
- **TYPE:** `bool`
62+
- **OPTIONAL:** false
63+
64+
65+
66+
## MODULES
67+
68+
### SomeModule
69+
70+
**KIND:** MODULE
71+
72+
**SOURCE:** src/MCPDocs.res:3:8
73+
74+
**MODULE_CONTENTS:**
75+
76+
#### ff
77+
78+
**KIND:** VALUE
79+
80+
**SIGNATURE:** `let ff: x`
81+
82+
**SOURCE:** src/MCPDocs.res:4:7
83+
84+
85+
86+
#### someVariant
87+
88+
**KIND:** TYPE
89+
90+
**SIGNATURE:** `@unboxed type someVariant = One(int) | Two(string)`
91+
92+
**SOURCE:** src/MCPDocs.res:7:3
93+
94+
95+
**VARIANT_CONSTRUCTORS:**
96+
- **CONSTRUCTOR:** `One`
97+
- **SIGNATURE:** `One(int)`
98+
99+
- **CONSTRUCTOR:** `Two`
100+
- **SIGNATURE:** `Two(string)`
101+
102+
103+
104+
105+
43106

44107
MCP docs for library
45-
No result.
108+
TODO
46109

0 commit comments

Comments
 (0)