diff --git a/analysis/bin/main.ml b/analysis/bin/main.ml index f25cb78115..7df7797278 100644 --- a/analysis/bin/main.ml +++ b/analysis/bin/main.ml @@ -110,6 +110,19 @@ let main () = path line col in match args with + | [_; "mcp"; "loc-info"; path; line; col] -> + Mcp.LocInfo.locInfo ~path ~pos:(int_of_string line, int_of_string col) + |> print_endline + | [_; "mcp"; "identifier-info"; path; identifier] -> + Mcp.IdentifierInfo.identifierInfo ~identifier ~path ~maybe_line:None + ~maybe_col:None + |> print_endline + | [_; "mcp"; "docs"; called_from; typ; identifier] -> ( + match Mcp.Docs.docs_type_from_string typ with + | None -> + print_endline + "Not a valid type. Should be either 'ProjectFile' or 'Library'." + | Some typ -> Mcp.Docs.docs ~called_from ~typ ~identifier |> print_endline) | [_; "cache-project"; rootPath] -> ( Cfg.readProjectConfigCache := false; let uri = Uri.fromPath rootPath in diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 464b3fa53d..20d61214a6 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -432,6 +432,36 @@ let test ~path = ("TypeDefinition " ^ path ^ " " ^ string_of_int line ^ ":" ^ string_of_int col); typeDefinition ~path ~pos:(line, col) ~debug:true + | "mli" -> + print_endline + ("MCP loc info " ^ path ^ " " ^ string_of_int line ^ ":" + ^ string_of_int col); + let currentFile = createCurrentFile () in + Mcp.LocInfo.locInfo ~path ~pos:(line, col) |> print_endline; + Sys.remove currentFile + | "mif" -> + print_endline + ("MCP identifier info " ^ path ^ " " ^ string_of_int line ^ ":" + ^ string_of_int col); + let currentFile = createCurrentFile () in + let identifier = String.sub rest 3 (String.length rest - 3) in + let identifier = String.trim identifier in + Mcp.IdentifierInfo.identifierInfo ~identifier ~path ~maybe_line:None + ~maybe_col:None + |> print_endline; + Sys.remove currentFile + | "mdp" -> + print_endline "MCP docs for project file"; + let identifier = String.sub rest 3 (String.length rest - 3) in + let identifier = String.trim identifier in + Mcp.Docs.docs ~called_from:path ~typ:ProjectFile ~identifier + |> print_endline + | "mdl" -> + print_endline "MCP docs for library"; + let identifier = String.sub rest 3 (String.length rest - 3) in + let identifier = String.trim identifier in + Mcp.Docs.docs ~called_from:path ~typ:Library ~identifier + |> print_endline | "xfm" -> let currentFile = createCurrentFile () in (* +2 is to ensure that the character ^ points to is what's considered the end of the selection. *) diff --git a/analysis/src/DocGen.ml b/analysis/src/DocGen.ml new file mode 100644 index 0000000000..89a3c5d60a --- /dev/null +++ b/analysis/src/DocGen.ml @@ -0,0 +1,942 @@ +module StringSet = Set.Make (String) + +type fieldDoc = { + fieldName: string; + docstrings: string list; + signature: string; + optional: bool; + deprecated: string option; +} + +type constructorPayload = InlineRecord of {fieldDocs: fieldDoc list} + +type constructorDoc = { + constructorName: string; + docstrings: string list; + signature: string; + deprecated: string option; + items: constructorPayload option; +} + +type typeDoc = {path: string; genericParameters: typeDoc list} +type valueSignature = {parameters: typeDoc list; returnType: typeDoc} + +type source = {filepath: string; line: int; col: int} + +type docItemDetail = + | Record of {fieldDocs: fieldDoc list} + | Variant of {constructorDocs: constructorDoc list} + | Signature of valueSignature + +type docItem = + | Value of { + id: string; + docstring: string list; + signature: string; + name: string; + deprecated: string option; + detail: docItemDetail option; + source: source; + } + | Type of { + id: string; + docstring: string list; + signature: string; + name: string; + deprecated: string option; + detail: docItemDetail option; + source: source; + (** Additional documentation for constructors and record fields, if available. *) + } + | Module of docsForModule + | ModuleType of { + id: string; + docstring: string list; + deprecated: string option; + name: string; + source: source; + items: docItem list; + } + | ModuleAlias of { + id: string; + docstring: string list; + name: string; + source: source; + items: docItem list; + } +and docsForModule = { + id: string; + docstring: string list; + deprecated: string option; + name: string; + moduletypeid: string option; + source: source; + items: docItem list; +} + +module JsonOutput = struct + let stringifyDocstrings docstrings = + let open Protocol in + docstrings + |> List.map (fun docstring -> docstring |> String.trim |> wrapInQuotes) + |> array + + let stringifyFieldDoc ~indentation (fieldDoc : fieldDoc) = + let open Protocol in + stringifyObject ~indentation:(indentation + 1) + [ + ("name", Some (wrapInQuotes fieldDoc.fieldName)); + ( "deprecated", + match fieldDoc.deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ("optional", Some (string_of_bool fieldDoc.optional)); + ("docstrings", Some (stringifyDocstrings fieldDoc.docstrings)); + ("signature", Some (wrapInQuotes fieldDoc.signature)); + ] + + let stringifyConstructorPayload ~indentation + (constructorPayload : constructorPayload) = + let open Protocol in + match constructorPayload with + | InlineRecord {fieldDocs} -> + stringifyObject ~indentation:(indentation + 1) + [ + ("kind", Some (wrapInQuotes "inlineRecord")); + ( "fields", + Some + (fieldDocs + |> List.map (stringifyFieldDoc ~indentation:(indentation + 1)) + |> array) ); + ] + + let rec stringifyTypeDoc ~indentation (td : typeDoc) : string = + let open Protocol in + let ps = + match td.genericParameters with + | [] -> None + | ts -> + ts |> List.map (stringifyTypeDoc ~indentation:(indentation + 1)) + |> fun ts -> Some (array ts) + in + + stringifyObject ~indentation:(indentation + 1) + [("path", Some (wrapInQuotes td.path)); ("genericTypeParameters", ps)] + + let stringifyDetail ?(indentation = 0) (detail : docItemDetail) = + let open Protocol in + match detail with + | Record {fieldDocs} -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("kind", Some (wrapInQuotes "record")); + ( "items", + Some + (fieldDocs |> List.map (stringifyFieldDoc ~indentation) |> array) + ); + ] + | Variant {constructorDocs} -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("kind", Some (wrapInQuotes "variant")); + ( "items", + Some + (constructorDocs + |> List.map (fun constructorDoc -> + stringifyObject ~startOnNewline:true + ~indentation:(indentation + 1) + [ + ( "name", + Some (wrapInQuotes constructorDoc.constructorName) ); + ( "deprecated", + match constructorDoc.deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ( "docstrings", + Some (stringifyDocstrings constructorDoc.docstrings) + ); + ( "signature", + Some (wrapInQuotes constructorDoc.signature) ); + ( "payload", + match constructorDoc.items with + | None -> None + | Some constructorPayload -> + Some + (stringifyConstructorPayload + ~indentation:(indentation + 1) + constructorPayload) ); + ]) + |> array) ); + ] + | Signature {parameters; returnType} -> + let ps = + match parameters with + | [] -> None + | ps -> + ps |> List.map (stringifyTypeDoc ~indentation:(indentation + 1)) + |> fun ps -> Some (array ps) + in + stringifyObject ~startOnNewline:true ~indentation + [ + ("kind", Some (wrapInQuotes "signature")); + ( "details", + Some + (stringifyObject ~startOnNewline:false ~indentation + [ + ("parameters", ps); + ( "returnType", + Some (stringifyTypeDoc ~indentation returnType) ); + ]) ); + ] + + let stringifySource ~indentation source = + let open Protocol in + stringifyObject ~startOnNewline:false ~indentation + [ + ("filepath", Some (source.filepath |> wrapInQuotes)); + ("line", Some (source.line |> string_of_int)); + ("col", Some (source.col |> string_of_int)); + ] + + let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) = + let open Protocol in + match item with + | Value {id; docstring; signature; name; deprecated; source; detail} -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes id)); + ("kind", Some (wrapInQuotes "value")); + ("name", Some (name |> wrapInQuotes)); + ( "deprecated", + match deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ("signature", Some (signature |> String.trim |> wrapInQuotes)); + ("docstrings", Some (stringifyDocstrings docstring)); + ( "source", + Some (stringifySource ~indentation:(indentation + 1) source) ); + ( "detail", + match detail with + | None -> None + | Some detail -> + Some (stringifyDetail ~indentation:(indentation + 1) detail) ); + ] + | Type {id; docstring; signature; name; deprecated; detail; source} -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes id)); + ("kind", Some (wrapInQuotes "type")); + ("name", Some (name |> wrapInQuotes)); + ( "deprecated", + match deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ("signature", Some (signature |> wrapInQuotes)); + ("docstrings", Some (stringifyDocstrings docstring)); + ( "source", + Some (stringifySource ~indentation:(indentation + 1) source) ); + ( "detail", + match detail with + | None -> None + | Some detail -> + Some (stringifyDetail ~indentation:(indentation + 1) detail) ); + ] + | Module m -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes m.id)); + ("name", Some (wrapInQuotes m.name)); + ("kind", Some (wrapInQuotes "module")); + ( "deprecated", + match m.deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ( "moduletypeid", + match m.moduletypeid with + | Some path -> Some (wrapInQuotes path) + | None -> None ); + ("docstrings", Some (stringifyDocstrings m.docstring)); + ( "source", + Some (stringifySource ~indentation:(indentation + 1) m.source) ); + ( "items", + Some + (m.items + |> List.map + (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) + |> array) ); + ] + | ModuleType m -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes m.id)); + ("name", Some (wrapInQuotes m.name)); + ("kind", Some (wrapInQuotes "moduleType")); + ( "deprecated", + match m.deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ("docstrings", Some (stringifyDocstrings m.docstring)); + ( "source", + Some (stringifySource ~indentation:(indentation + 1) m.source) ); + ( "items", + Some + (m.items + |> List.map + (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) + |> array) ); + ] + | ModuleAlias m -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes m.id)); + ("kind", Some (wrapInQuotes "moduleAlias")); + ("name", Some (wrapInQuotes m.name)); + ("docstrings", Some (stringifyDocstrings m.docstring)); + ( "source", + Some (stringifySource ~indentation:(indentation + 1) m.source) ); + ( "items", + Some + (m.items + |> List.map + (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) + |> array) ); + ] + + and stringifyDocsForModule ?(indentation = 0) ~originalEnv (d : docsForModule) + = + let open Protocol in + stringifyObject ~startOnNewline:true ~indentation + [ + ("name", Some (wrapInQuotes d.name)); + ( "deprecated", + match d.deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ("docstrings", Some (stringifyDocstrings d.docstring)); + ( "source", + Some (stringifySource ~indentation:(indentation + 1) d.source) ); + ( "items", + Some + (d.items + |> List.map + (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) + |> array) ); + ] +end + +module MdOutput = struct + let stringifyDocstrings docstrings = + match docstrings with + | [] -> "" + | docstrings -> + docstrings |> List.map String.trim + |> List.filter (fun s -> s <> "") + |> String.concat "\n\n" + + let stringifyFieldDoc (fieldDoc : fieldDoc) = + let buffer = Buffer.create 256 in + Buffer.add_string buffer + (Printf.sprintf "- **FIELD:** `%s`\n" fieldDoc.fieldName); + Buffer.add_string buffer + (Printf.sprintf " - **TYPE:** `%s`\n" fieldDoc.signature); + Buffer.add_string buffer + (Printf.sprintf " - **OPTIONAL:** %s\n" + (string_of_bool fieldDoc.optional)); + (match fieldDoc.deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf " - **DEPRECATED:** %s\n" d) + | None -> ()); + (match stringifyDocstrings fieldDoc.docstrings with + | "" -> () + | docs -> + Buffer.add_string buffer (Printf.sprintf " - **DESCRIPTION:** %s\n" docs)); + Buffer.contents buffer + + let stringifyConstructorDoc (constructorDoc : constructorDoc) = + let buffer = Buffer.create 256 in + Buffer.add_string buffer + (Printf.sprintf "- **CONSTRUCTOR:** `%s`\n" constructorDoc.constructorName); + Buffer.add_string buffer + (Printf.sprintf " - **SIGNATURE:** `%s`\n" constructorDoc.signature); + (match constructorDoc.deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf " - **DEPRECATED:** %s\n" d) + | None -> ()); + (match stringifyDocstrings constructorDoc.docstrings with + | "" -> () + | docs -> + Buffer.add_string buffer (Printf.sprintf " - **DESCRIPTION:** %s\n" docs)); + (match constructorDoc.items with + | None -> () + | Some (InlineRecord {fieldDocs}) -> + Buffer.add_string buffer " - **INLINE_RECORD_FIELDS:**\n"; + fieldDocs + |> List.iter (fun field -> + let field_lines = + stringifyFieldDoc field |> String.split_on_char '\n' + in + field_lines + |> List.iter (fun line -> + if String.trim line <> "" then + Buffer.add_string buffer (" " ^ line ^ "\n")))); + Buffer.contents buffer + + let stringifyDetail (detail : docItemDetail) = + match detail with + | Record {fieldDocs} -> + let buffer = Buffer.create 512 in + Buffer.add_string buffer "\n**RECORD_FIELDS:**\n"; + fieldDocs + |> List.iter (fun field -> + Buffer.add_string buffer (stringifyFieldDoc field ^ "\n")); + Buffer.contents buffer + | Variant {constructorDocs} -> + let buffer = Buffer.create 512 in + Buffer.add_string buffer "\n**VARIANT_CONSTRUCTORS:**\n"; + constructorDocs + |> List.iter (fun constructor -> + Buffer.add_string buffer + (stringifyConstructorDoc constructor ^ "\n")); + Buffer.contents buffer + | Signature {parameters = _; returnType = _} -> + (* For function signatures, we could add parameter details, but for now keep it simple *) + "" + + let stringifySource (source : source) = + Printf.sprintf "**SOURCE:** %s:%d:%d" source.filepath source.line source.col + + let rec stringifyDocItem ?(depth = 2) (item : docItem) = + let heading = String.make depth '#' ^ " " in + + match item with + | Value {name; docstring; signature; deprecated; detail; source} -> + let buffer = Buffer.create 1024 in + Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading name); + Buffer.add_string buffer "**KIND:** VALUE\n\n"; + Buffer.add_string buffer + (Printf.sprintf "**SIGNATURE:** `%s`\n\n" signature); + Buffer.add_string buffer + (Printf.sprintf "%s\n\n" (stringifySource source)); + (match deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d) + | None -> ()); + (match stringifyDocstrings docstring with + | "" -> () + | docs -> + Buffer.add_string buffer + (Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs)); + (match detail with + | None -> () + | Some d -> Buffer.add_string buffer (stringifyDetail d ^ "\n")); + Buffer.contents buffer + | Type {name; docstring; signature; deprecated; detail; source} -> + let buffer = Buffer.create 1024 in + Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading name); + Buffer.add_string buffer "**KIND:** TYPE\n\n"; + Buffer.add_string buffer + (Printf.sprintf "**SIGNATURE:** `%s`\n\n" signature); + Buffer.add_string buffer + (Printf.sprintf "%s\n\n" (stringifySource source)); + (match deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d) + | None -> ()); + (match stringifyDocstrings docstring with + | "" -> () + | docs -> + Buffer.add_string buffer + (Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs)); + (match detail with + | None -> () + | Some d -> Buffer.add_string buffer (stringifyDetail d ^ "\n")); + Buffer.contents buffer + | Module m -> + let buffer = Buffer.create 1024 in + Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading m.name); + Buffer.add_string buffer "**KIND:** MODULE\n\n"; + Buffer.add_string buffer + (Printf.sprintf "%s\n\n" (stringifySource m.source)); + (match m.moduletypeid with + | Some id -> + Buffer.add_string buffer + (Printf.sprintf "**MODULE_TYPE_ID:** %s\n\n" id) + | None -> ()); + (match m.deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d) + | None -> ()); + (match stringifyDocstrings m.docstring with + | "" -> () + | docs -> + Buffer.add_string buffer + (Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs)); + (match m.items with + | [] -> () + | items -> + Buffer.add_string buffer "**MODULE_CONTENTS:**\n\n"; + items + |> List.iter (fun item -> + Buffer.add_string buffer + (stringifyDocItem ~depth:(depth + 1) item ^ "\n"))); + Buffer.contents buffer + | ModuleType m -> + let buffer = Buffer.create 1024 in + Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading m.name); + Buffer.add_string buffer "**KIND:** MODULE_TYPE\n\n"; + Buffer.add_string buffer + (Printf.sprintf "%s\n\n" (stringifySource m.source)); + (match m.deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d) + | None -> ()); + (match stringifyDocstrings m.docstring with + | "" -> () + | docs -> + Buffer.add_string buffer + (Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs)); + (match m.items with + | [] -> () + | items -> + Buffer.add_string buffer "**MODULE_TYPE_CONTENTS:**\n\n"; + items + |> List.iter (fun item -> + Buffer.add_string buffer + (stringifyDocItem ~depth:(depth + 1) item ^ "\n"))); + Buffer.contents buffer + | ModuleAlias m -> + let buffer = Buffer.create 1024 in + Buffer.add_string buffer (Printf.sprintf "%s%s\n\n" heading m.name); + Buffer.add_string buffer "**KIND:** MODULE_ALIAS\n\n"; + Buffer.add_string buffer + (Printf.sprintf "%s\n\n" (stringifySource m.source)); + (match stringifyDocstrings m.docstring with + | "" -> () + | docs -> + Buffer.add_string buffer + (Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs)); + (match m.items with + | [] -> () + | items -> + Buffer.add_string buffer "**ALIASED_MODULE_CONTENTS:**\n\n"; + items + |> List.iter (fun item -> + Buffer.add_string buffer + (stringifyDocItem ~depth:(depth + 1) item ^ "\n"))); + Buffer.contents buffer + + let stringifyDocsForModule (docs : docsForModule) = + let buffer = Buffer.create 4096 in + + (* Header with metadata *) + Buffer.add_string buffer (Printf.sprintf "# %s\n\n" docs.name); + Buffer.add_string buffer "**KIND:** MODULE\n\n"; + Buffer.add_string buffer + (Printf.sprintf "%s\n\n" (stringifySource docs.source)); + + (match docs.deprecated with + | Some d -> + Buffer.add_string buffer (Printf.sprintf "**DEPRECATED:** %s\n\n" d) + | None -> ()); + + (match stringifyDocstrings docs.docstring with + | "" -> () + | docs_str -> + Buffer.add_string buffer + (Printf.sprintf "**DESCRIPTION:**\n%s\n\n" docs_str)); + + (* Group items by type for better organization *) + let values, types, modules, module_types, module_aliases = + docs.items + |> List.fold_left + (fun (vals, typs, mods, mod_typs, mod_aliases) item -> + match item with + | Value _ -> (item :: vals, typs, mods, mod_typs, mod_aliases) + | Type _ -> (vals, item :: typs, mods, mod_typs, mod_aliases) + | Module _ -> (vals, typs, item :: mods, mod_typs, mod_aliases) + | ModuleType _ -> (vals, typs, mods, item :: mod_typs, mod_aliases) + | ModuleAlias _ -> (vals, typs, mods, mod_typs, item :: mod_aliases)) + ([], [], [], [], []) + in + + let values = List.rev values in + let types = List.rev types in + let modules = List.rev modules in + let module_types = List.rev module_types in + let module_aliases = List.rev module_aliases in + + (* Content sections with clear headers *) + (match values with + | [] -> () + | _ -> + Buffer.add_string buffer "## VALUES\n\n"; + values + |> List.iter (fun item -> + Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n"))); + + (match types with + | [] -> () + | _ -> + Buffer.add_string buffer "## TYPES\n\n"; + types + |> List.iter (fun item -> + Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n"))); + + (match modules with + | [] -> () + | _ -> + Buffer.add_string buffer "## MODULES\n\n"; + modules + |> List.iter (fun item -> + Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n"))); + + (match module_types with + | [] -> () + | _ -> + Buffer.add_string buffer "## MODULE_TYPES\n\n"; + module_types + |> List.iter (fun item -> + Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n"))); + + (match module_aliases with + | [] -> () + | _ -> + Buffer.add_string buffer "## MODULE_ALIASES\n\n"; + module_aliases + |> List.iter (fun item -> + Buffer.add_string buffer (stringifyDocItem ~depth:3 item ^ "\n"))); + + Buffer.contents buffer +end + +let fieldToFieldDoc (field : SharedTypes.field) : fieldDoc = + { + fieldName = field.fname.txt; + docstrings = field.docstring; + optional = field.optional; + signature = Shared.typeToString field.typ; + deprecated = field.deprecated; + } + +let typeDetail typ ~env ~full = + let open SharedTypes in + match TypeUtils.extractTypeFromResolvedType ~env ~full typ with + | Some (Trecord {fields}) -> + Some (Record {fieldDocs = fields |> List.map fieldToFieldDoc}) + | Some (Tvariant {constructors}) -> + Some + (Variant + { + constructorDocs = + constructors + |> List.map (fun (c : Constructor.t) -> + { + constructorName = c.cname.txt; + docstrings = c.docstring; + signature = CompletionBackEnd.showConstructor c; + deprecated = c.deprecated; + items = + (match c.args with + | InlineRecord fields -> + Some + (InlineRecord + {fieldDocs = fields |> List.map fieldToFieldDoc}) + | _ -> None); + }); + }) + | _ -> None + +(* split a list into two parts all the items except the last one and the last item *) +let splitLast l = + let rec splitLast' acc = function + | [] -> failwith "splitLast: empty list" + | [x] -> (List.rev acc, x) + | x :: xs -> splitLast' (x :: acc) xs + in + splitLast' [] l + +let path_to_string path = + let buf = Buffer.create 64 in + let rec aux = function + | Path.Pident id -> Buffer.add_string buf (Ident.name id) + | Path.Pdot (p, s, _) -> + aux p; + Buffer.add_char buf '.'; + Buffer.add_string buf s + | Path.Papply (p1, p2) -> + aux p1; + Buffer.add_char buf '('; + aux p2; + Buffer.add_char buf ')' + in + aux path; + Buffer.contents buf + +let valueDetail (typ : Types.type_expr) = + let rec collectSignatureTypes (typ : Types.type_expr) = + match typ.desc with + | Tlink t | Tsubst t | Tpoly (t, []) -> collectSignatureTypes t + | Tconstr (path, ts, _) -> ( + let p = path_to_string path in + match ts with + | [] -> [{path = p; genericParameters = []}] + | ts -> + let ts = + ts + |> List.concat_map (fun (t : Types.type_expr) -> + collectSignatureTypes t) + in + [{path = p; genericParameters = ts}]) + | Tarrow (_, t1, t2, _, _) -> + collectSignatureTypes t1 @ collectSignatureTypes t2 + | Tvar None -> [{path = "_"; genericParameters = []}] + | _ -> [] + in + match collectSignatureTypes typ with + | [] -> None + | ts -> + let parameters, returnType = splitLast ts in + Some (Signature {parameters; returnType}) + +let makeId modulePath ~identifier = + identifier :: modulePath |> List.rev |> SharedTypes.ident + +let getSource ~rootPath ({loc_start} : Location.t) = + let line, col = Pos.ofLexing loc_start in + let filepath = + Files.relpath rootPath loc_start.pos_fname + |> Files.split Filename.dir_sep + |> String.concat "/" + in + {filepath; line = line + 1; col = col + 1} + +let extractDocs ~entryPointFile ~debug = + let path = + match Filename.is_relative entryPointFile with + | true -> Unix.realpath entryPointFile + | false -> entryPointFile + in + if debug then Printf.printf "extracting docs for %s\n" path; + let result = + match + FindFiles.isImplementation path = false + && FindFiles.isInterface path = false + with + | false -> ( + let path = + if FindFiles.isImplementation path then + let pathAsResi = + (path |> Filename.dirname) ^ "/" + ^ (path |> Filename.basename |> Filename.chop_extension) + ^ ".resi" + in + if Sys.file_exists pathAsResi then ( + if debug then + Printf.printf "preferring found resi file for impl: %s\n" + pathAsResi; + pathAsResi) + else path + else path + in + match Cmt.loadFullCmtFromPath ~path with + | None -> + Error + (Printf.sprintf + "error: failed to generate doc for %s, try to build the project" + path) + | Some full -> + let file = full.file in + let structure = file.structure in + let rootPath = full.package.rootPath in + let open SharedTypes in + let env = QueryEnv.fromFile file in + let rec extractDocsForModule ?(modulePath = [env.file.moduleName]) + (structure : Module.structure) = + let valuesSeen = ref StringSet.empty in + { + id = modulePath |> List.rev |> ident; + docstring = structure.docstring |> List.map String.trim; + name = structure.name; + moduletypeid = None; + deprecated = structure.deprecated; + source = + { + filepath = + (match rootPath = "." with + | true -> file.uri |> Uri.toPath + | false -> + Files.relpath rootPath (file.uri |> Uri.toPath) + |> Files.split Filename.dir_sep + |> String.concat "/"); + line = 1; + col = 1; + }; + items = + structure.items + |> List.filter_map (fun (item : Module.item) -> + let item = + { + item with + name = Ext_ident.unwrap_uppercase_exotic item.name; + } + in + let source = getSource ~rootPath item.loc in + match item.kind with + | Value typ -> + Some + (Value + { + id = modulePath |> makeId ~identifier:item.name; + docstring = item.docstring |> List.map String.trim; + signature = + "let " ^ item.name ^ ": " + ^ Shared.typeToString typ; + name = item.name; + deprecated = item.deprecated; + detail = valueDetail typ; + source; + }) + | Type (typ, _) -> + Some + (Type + { + id = modulePath |> makeId ~identifier:item.name; + docstring = item.docstring |> List.map String.trim; + signature = + typ.decl |> Shared.declToString item.name; + name = item.name; + deprecated = item.deprecated; + detail = typeDetail typ ~full ~env; + source; + }) + | Module {type_ = Ident p; isModuleType = false} -> + (* module Whatever = OtherModule *) + let aliasToModule = p |> pathIdentToString in + let id = + (modulePath |> List.rev |> List.hd) ^ "." ^ item.name + in + let items, internalDocstrings = + match + ProcessCmt.fileForModule ~package:full.package + aliasToModule + with + | None -> ([], []) + | Some file -> + let docs = + extractDocsForModule ~modulePath:[id] + file.structure + in + (docs.items, docs.docstring) + in + Some + (ModuleAlias + { + id; + name = item.name; + source; + items; + docstring = + item.docstring @ internalDocstrings + |> List.map String.trim; + }) + | Module {type_ = Structure m; isModuleType = false} -> + (* module Whatever = {} in res or module Whatever: {} in resi. *) + let modulePath = m.name :: modulePath in + let docs = extractDocsForModule ~modulePath m in + Some + (Module + { + id = modulePath |> List.rev |> ident; + name = m.name; + moduletypeid = None; + docstring = item.docstring @ m.docstring; + deprecated = item.deprecated; + source; + items = docs.items; + }) + | Module {type_ = Structure m; isModuleType = true} -> + (* module type Whatever = {} *) + let modulePath = m.name :: modulePath in + let docs = extractDocsForModule ~modulePath m in + Some + (ModuleType + { + id = modulePath |> List.rev |> ident; + name = m.name; + docstring = item.docstring @ m.docstring; + deprecated = item.deprecated; + source; + items = docs.items; + }) + | Module + { + type_ = + Constraint (Structure _impl, Structure interface); + } -> + (* module Whatever: { } = { }. Prefer the interface. *) + Some + (Module + (extractDocsForModule + ~modulePath:(interface.name :: modulePath) + interface)) + | Module {type_ = Constraint (Structure m, Ident p)} -> + (* module M: T = { }. Print M *) + let docs = + extractDocsForModule ~modulePath:(m.name :: modulePath) + m + in + let identModulePath = p |> Path.head |> Ident.name in + + let moduleTypeIdPath = + match + ProcessCmt.fileForModule ~package:full.package + identModulePath + |> Option.is_none + with + | false -> [] + | true -> [modulePath |> List.rev |> List.hd] + in + + Some + (Module + { + docs with + moduletypeid = + Some + (makeId ~identifier:(Path.name p) + moduleTypeIdPath); + }) + | _ -> None) + (* Filter out shadowed bindings by keeping only the last value associated with an id *) + |> List.rev + |> List.filter_map (fun (docItem : docItem) -> + match docItem with + | Value {id} -> + if StringSet.mem id !valuesSeen then None + else ( + valuesSeen := StringSet.add id !valuesSeen; + Some docItem) + | _ -> Some docItem) + |> List.rev; + } + in + let docs = extractDocsForModule structure in + Ok (docs, env)) + | true -> + Error + (Printf.sprintf + "error: failed to read %s, expected an .res or .resi file" path) + in + + result + +let extractDocsToJson ~entryPointFile ~debug = + match extractDocs ~entryPointFile ~debug with + | Ok (docs, env) -> + Ok (JsonOutput.stringifyDocsForModule ~originalEnv:env docs) + | Error e -> Error e + +let extractDocsToMd ~entryPointFile ~debug = + match extractDocs ~entryPointFile ~debug with + | Ok (docs, _env) -> Ok (MdOutput.stringifyDocsForModule docs) + | Error e -> Error e diff --git a/analysis/src/Hover.ml b/analysis/src/Hover.ml index c66ac8f787..4916e3649b 100644 --- a/analysis/src/Hover.ml +++ b/analysis/src/Hover.ml @@ -118,13 +118,12 @@ let expandTypes ~file ~package ~supportsMarkdownLinks typ = `InlineType ) | all -> let typesSeen = ref StringSet.empty in - let typeId ~(env : QueryEnv.t) ~name = - env.file.moduleName :: List.rev (name :: env.pathRev) |> String.concat "." - in ( all (* Don't produce duplicate type definitions for recursive types *) - |> List.filter (fun {env; name} -> - let typeId = typeId ~env ~name in + |> List.filter (fun {env; name; loc} -> + let typeId = + TypeUtils.typeId ~env ~name:(Location.mkloc name loc) + in if StringSet.mem typeId !typesSeen then false else ( typesSeen := StringSet.add typeId !typesSeen; diff --git a/analysis/src/Mcp.ml b/analysis/src/Mcp.ml new file mode 100644 index 0000000000..a1cc03e374 --- /dev/null +++ b/analysis/src/Mcp.ml @@ -0,0 +1,248 @@ +open SharedTypes + +module StringSet = Set.Make (String) + +module Utils = struct + let wrapInTag content ~tag = Printf.sprintf "<%s>\n%s\n" tag content tag + let wrapInTagOpt content ~tag = + match content with + | None -> None + | Some content -> Some (wrapInTag content ~tag) + + let print_type_with_related_types ~full t = + let {TypeUtils.ExpandType.mainTypes; relatedTypes} = + TypeUtils.ExpandType.expandTypes t ~full + in + Some + (Printf.sprintf + "\n\ + %s\n\ + \n\n\ + \n\ + %s\n\ + " + (mainTypes + |> List.map (fun (input : TypeUtils.ExpandType.expandTypeInput) -> + match input with + | TypeUtils.ExpandType.TypeExpr {typeExpr} -> + Shared.typeToString typeExpr + | TypeUtils.ExpandType.TypeDecl {name; typeDecl} -> + Shared.declToString name.txt typeDecl) + |> String.concat "\n\n") + (relatedTypes + |> List.map (fun (input : TypeUtils.ExpandType.expandTypeInput) -> + match input with + | TypeUtils.ExpandType.TypeExpr {typeExpr} -> + Shared.typeToString typeExpr + | TypeUtils.ExpandType.TypeDecl {name; typeDecl} -> + Shared.declToString name.txt typeDecl) + |> String.concat "\n\n")) +end + +module LocInfo = struct + let showModule = Hover.showModule + + (* LocInfo thoughts: + - Check for long variants and/or records, and do not expand them automatically. DomProps being one example. *) + + let locInfo ~path ~pos = + let debug = false in + let result = + match Cmt.loadFullCmtFromPath ~path with + | None -> None + | Some full -> ( + match References.getLocItem ~full ~pos ~debug with + | None -> None + | Some locItem -> ( + let isModule = + match locItem.locType with + | LModule _ | TopLevelModule _ -> true + | TypeDefinition _ | Typed _ | Constant _ -> false + in + let uriLocOpt = References.definitionForLocItem ~full locItem in + let skipZero = + match uriLocOpt with + | None -> false + | Some (_, loc) -> + let isInterface = full.file.uri |> Uri.isInterface in + let posIsZero {Lexing.pos_lnum; pos_bol; pos_cnum} = + (not isInterface) && pos_lnum = 1 && pos_cnum - pos_bol = 0 + in + (* Skip if range is all zero, unless it's a module *) + (not isModule) && posIsZero loc.loc_start && posIsZero loc.loc_end + in + if skipZero then None + else + let file = full.file in + let package = full.package in + match locItem.locType with + | TypeDefinition + (name, ({type_manifest = Some tmanifest} as decl), _stamp) -> + Some + (Shared.declToString name decl + ^ "\n\n" + ^ Shared.typeToString tmanifest) + | TypeDefinition (name, decl, _stamp) -> + Some (Shared.declToString name decl) + | LModule (Definition (stamp, _tip)) + | LModule (LocalReference (stamp, _tip)) -> ( + match Stamps.findModule file.stamps stamp with + | None -> None + | Some md -> ( + match References.resolveModuleReference ~file ~package md with + | None -> None + | Some (file, declared) -> + let name, docstring = + match declared with + | Some d -> (d.name.txt, d.docstring) + | None -> (file.moduleName, file.structure.docstring) + in + showModule ~docstring ~name ~file declared ~package)) + | LModule (GlobalReference (moduleName, path, tip)) -> ( + match ProcessCmt.fileForModule ~package moduleName with + | None -> None + | Some file -> ( + let env = QueryEnv.fromFile file in + match References.exportedForTip ~env ~path ~package ~tip with + | None -> None + | Some (_env, _name, stamp) -> ( + match Stamps.findModule file.stamps stamp with + | None -> None + | Some md -> ( + match + References.resolveModuleReference ~file ~package md + with + | None -> None + | Some (file, declared) -> + let name, docstring = + match declared with + | Some d -> (d.name.txt, d.docstring) + | None -> (file.moduleName, file.structure.docstring) + in + showModule ~docstring ~name ~file ~package declared)))) + | LModule NotFound -> None + | TopLevelModule name -> ( + match ProcessCmt.fileForModule ~package name with + | None -> None + | Some file -> + showModule ~docstring:file.structure.docstring + ~name:file.moduleName ~file ~package None) + | Typed (name, t, _) -> + Utils.print_type_with_related_types ~full + (TypeUtils.ExpandType.TypeExpr + { + typeExpr = t; + name = Some (Location.mkloc name locItem.loc); + env = QueryEnv.fromFile full.file; + }) + | Constant t -> + Some + (match t with + | Const_int _ -> "int" + | Const_char _ -> "char" + | Const_string _ -> "string" + | Const_float _ -> "float" + | Const_int32 _ -> "int32" + | Const_int64 _ -> "int64" + | Const_bigint _ -> "bigint"))) + in + match result with + | None -> "No result." + | Some s -> s +end + +module IdentifierInfo = struct + let identifierInfo ~identifier ~path ~maybe_line ~maybe_col = + (* TODO: Pull out the right scope + opens if these are set. *) + ignore maybe_line; + ignore maybe_col; + + let result = + match Cmt.loadFullCmtFromPath ~path with + | None -> None + | Some full -> + let env = QueryEnv.fromFile full.file in + let scope = Scope.create () in + let opens = [] in + let pos = (0, 0) in + let path = identifier |> String.split_on_char '.' in + let hd_opt l = List.nth_opt l 0 in + let print_completion_item (item : Completion.t) = + match item.kind with + | Value t -> + Some + (Printf.sprintf "Value %s:\n\n%s\n" identifier + (match + Utils.print_type_with_related_types ~full + (TypeUtils.ExpandType.TypeExpr + { + typeExpr = t; + name = Some (Location.mknoloc item.name); + env = QueryEnv.fromFile full.file; + }) + with + | None -> "" + | Some s -> s)) + | Type {decl; name} -> + Some + (Printf.sprintf "Type %s:\n\n%s\n" identifier + (Shared.declToString name decl)) + (*| Module {module_} -> Some (LocInfo.showModule)*) + | _ -> None + in + let value_completions = + CompletionBackEnd.getCompletionsForPath ~exact:true ~debug:false ~full + ~completionContext:Value ~pos ~env ~scope ~opens path + |> hd_opt + in + let type_completions = + CompletionBackEnd.getCompletionsForPath ~exact:true ~debug:false ~full + ~completionContext:Type ~pos ~env ~scope ~opens path + |> hd_opt + in + let module_completions = + CompletionBackEnd.getCompletionsForPath ~exact:true ~debug:false ~full + ~completionContext:Module ~pos ~env ~scope ~opens path + |> hd_opt + in + Some + ([ + (match value_completions with + | None -> None + | Some c -> print_completion_item c); + (match type_completions with + | None -> None + | Some c -> print_completion_item c); + (match module_completions with + | None -> None + | Some c -> print_completion_item c); + ] + |> List.filter_map (fun x -> x) + |> String.concat "\n\n") + in + match result with + | None -> "No result." + | Some s -> s +end + +module Docs = struct + type docsType = ProjectFile | Library + let docs_type_from_string = function + | "ProjectFile" -> Some ProjectFile + | "Library" -> Some Library + | _ -> None + + let docs ~called_from ~(typ : docsType) ~identifier = + let result = + match Cmt.loadFullCmtFromPath ~path:called_from with + | None -> Error "Could not load cmt file" + | Some _full -> ( + match typ with + | ProjectFile -> + DocGen.extractDocsToMd ~entryPointFile:identifier ~debug:false + | Library -> Ok "TODO") + in + match result with + | Error e -> "Error: " ^ e + | Ok s -> s +end diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 99ffca3f28..70098dd5e4 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1179,6 +1179,12 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot | Some posOfDot -> Some (makeAdditionalTextEditsForRemovingDot posOfDot)); } +(** Light weight type id *) +let typeId ~(env : QueryEnv.t) ~(name : string Location.loc) = + (env.file.moduleName :: List.rev (name.txt :: env.pathRev) + |> String.concat ".") + ^ ":" ^ Loc.toString name.loc + (** This takes a type expr and the env that type expr was found in, and produces a globally unique id for that specific type. The globally unique id is the full path to the type as seen from the root of the project. Example: type x in module SomeModule in file SomeFile would get the globally @@ -1285,3 +1291,256 @@ let completionPathFromMaybeBuiltin path = (* Route Stdlib_X to Stdlib.X for proper completions without the Stdlib_ prefix *) Some (String.split_on_char '_' mainModule) | _ -> None) + +module ExpandType = struct + type expandTypeInput = + | TypeExpr of { + typeExpr: Types.type_expr; + name: string Location.loc option; + env: QueryEnv.t; + } + | TypeDecl of { + typeDecl: Types.type_declaration; + name: string Location.loc; + env: QueryEnv.t; + } + + type expandTypeReturn = { + mainTypes: expandTypeInput list; + relatedTypes: expandTypeInput list; + } + + module TypeIdSet = Set.Make (String) + + let expandTypes (input : expandTypeInput) ~(full : SharedTypes.full) = + let rootEnv = QueryEnv.fromFile full.file in + + let expandTypeInputToKey = function + | TypeExpr {name; env} -> + typeId ~env + ~name: + (match name with + | None -> Location.mkloc "" Location.none + | Some n -> n) + | TypeDecl {name; env} -> typeId ~env ~name + in + + let deduplicateAndRemoveAlreadyPresent mainTypes relatedTypes = + let mainIds = ref TypeIdSet.empty in + let dedupedMain = + mainTypes + |> List.fold_left + (fun acc item -> + let id = expandTypeInputToKey item in + if TypeIdSet.mem id !mainIds then acc + else ( + mainIds := TypeIdSet.add id !mainIds; + item :: acc)) + [] + |> List.rev + in + + let relatedIds = ref TypeIdSet.empty in + let dedupedRelated = + relatedTypes + |> List.fold_left + (fun acc item -> + let id = expandTypeInputToKey item in + if TypeIdSet.mem id !mainIds || TypeIdSet.mem id !relatedIds then + acc + else ( + relatedIds := TypeIdSet.add id !relatedIds; + item :: acc)) + [] + |> List.rev + in + + (dedupedMain, dedupedRelated) + in + + let rec followTypeAliases acc (typeExpr : Types.type_expr) = + match typeExpr.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> followTypeAliases acc t1 + | Tconstr (path, typeArgs, _) -> ( + match + References.digConstructor ~env:rootEnv ~package:full.package path + with + | Some + ( env, + { + name; + item = {decl = {type_manifest = Some t1; type_params} as decl}; + } ) -> + let instantiated = + instantiateType ~typeParams:type_params ~typeArgs t1 + in + let currentAlias = TypeDecl {typeDecl = decl; name; env} in + followTypeAliases (currentAlias :: acc) instantiated + | Some (env, {name; item = {decl}}) -> + TypeDecl {typeDecl = decl; name; env} :: acc + | None -> acc) + | _ -> acc + in + + let rec findFinalConcreteType (typeExpr : Types.type_expr) = + match typeExpr.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findFinalConcreteType t1 + | Tconstr (path, typeArgs, _) -> ( + match + References.digConstructor ~env:rootEnv ~package:full.package path + with + | Some (_env, {item = {decl = {type_manifest = Some t1; type_params}}}) + -> + let instantiated = + instantiateType ~typeParams:type_params ~typeArgs t1 + in + findFinalConcreteType instantiated + | _ -> typeExpr) + | _ -> typeExpr + in + + let rec extractRelevantTypesFromTypeExpr ?(depth = 0) + (typeExpr : Types.type_expr) = + if depth > 1 then [] + else + match typeExpr.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> + extractRelevantTypesFromTypeExpr ~depth t1 + | Tconstr (path, typeArgs, _) -> + let constructorTypes = + match + References.digConstructor ~env:rootEnv ~package:full.package path + with + | Some (env, {name; item = {kind = Record fields; decl}}) -> + TypeDecl {typeDecl = decl; name; env} + :: + (if depth = 0 then + fields + |> List.fold_left + (fun acc field -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:(depth + 1) + field.typ) + [] + else []) + | Some (env, {name; item = {kind = Variant constructors; decl}}) -> + TypeDecl {typeDecl = decl; name; env} + :: + (if depth = 0 then + constructors + |> List.fold_left + (fun acc (constructor : Constructor.t) -> + match constructor.args with + | Args args -> + args + |> List.fold_left + (fun acc (argType, _) -> + acc + @ extractRelevantTypesFromTypeExpr + ~depth:(depth + 1) argType) + acc + | InlineRecord fields -> + fields + |> List.fold_left + (fun acc field -> + acc + @ extractRelevantTypesFromTypeExpr + ~depth:(depth + 1) field.typ) + acc) + [] + else []) + | Some (_env, {item = {decl = {type_manifest = Some t1}}}) -> + extractRelevantTypesFromTypeExpr ~depth t1 + | _ -> [] + in + let typeArgTypes = + typeArgs + |> List.fold_left + (fun acc typeArg -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:(depth + 1) typeArg) + [] + in + constructorTypes @ typeArgTypes + | Tvariant {row_fields} when depth = 0 -> + row_fields + |> List.fold_left + (fun acc (_label, field) -> + match field with + | Types.Rpresent (Some typeExpr) -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:(depth + 1) + typeExpr + | Reither (_, typeExprs, _, _) -> + typeExprs + |> List.fold_left + (fun acc typeExpr -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:(depth + 1) + typeExpr) + acc + | _ -> acc) + [] + | _ -> [] + in + + let extractRelevantTypesFromTypeDecl (typeDecl : Types.type_declaration) = + match typeDecl.type_manifest with + | Some typeExpr -> extractRelevantTypesFromTypeExpr typeExpr + | None -> ( + match typeDecl.type_kind with + | Type_record (label_declarations, _) -> + label_declarations + |> List.fold_left + (fun acc (label_decl : Types.label_declaration) -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:1 label_decl.ld_type) + [] + | Type_variant constructor_declarations -> + constructor_declarations + |> List.fold_left + (fun acc (constructor_decl : Types.constructor_declaration) -> + match constructor_decl.cd_args with + | Cstr_tuple type_exprs -> + type_exprs + |> List.fold_left + (fun acc type_expr -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:1 type_expr) + acc + | Cstr_record label_declarations -> + label_declarations + |> List.fold_left + (fun acc (label_decl : Types.label_declaration) -> + acc + @ extractRelevantTypesFromTypeExpr ~depth:1 + label_decl.ld_type) + acc) + [] + | Type_abstract | Type_open -> []) + in + + match input with + | TypeExpr {typeExpr; name; env} -> + let aliases = followTypeAliases [] typeExpr in + let mainTypesRaw = TypeExpr {typeExpr; name; env} :: aliases in + + (* Extract related types from the final concrete type *) + let finalConcreteType = findFinalConcreteType typeExpr in + let relatedTypesRaw = + extractRelevantTypesFromTypeExpr finalConcreteType + in + + let mainTypes, relatedTypes = + deduplicateAndRemoveAlreadyPresent mainTypesRaw relatedTypesRaw + in + {mainTypes; relatedTypes} + | TypeDecl {typeDecl} -> + let mainTypes = [input] in + let relatedTypesRaw = extractRelevantTypesFromTypeDecl typeDecl in + + let _, relatedTypes = + deduplicateAndRemoveAlreadyPresent mainTypes relatedTypesRaw + in + {mainTypes; relatedTypes} +end diff --git a/mcp/.editorconfig b/mcp/.editorconfig new file mode 100644 index 0000000000..025dd8c8b1 --- /dev/null +++ b/mcp/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true diff --git a/mcp/.gitattributes b/mcp/.gitattributes new file mode 100644 index 0000000000..af3ad12812 --- /dev/null +++ b/mcp/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/** linguist-vendored +/.yarn/releases/* binary +/.yarn/plugins/**/* binary +/.pnp.* binary linguist-generated diff --git a/mcp/.gitignore b/mcp/.gitignore new file mode 100644 index 0000000000..1cc330803b --- /dev/null +++ b/mcp/.gitignore @@ -0,0 +1,18 @@ +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Whether you use PnP or not, the node_modules folder is often used to store +# build artifacts that should be gitignored +node_modules + +# Swap the comments on the following lines if you wish to use zero-installs +# In that case, don't forget to run `yarn config set enableGlobalCache false`! +# Documentation here: https://yarnpkg.com/features/caching#zero-installs + +#!.yarn/cache +.pnp.* +.cache \ No newline at end of file diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 0000000000..6276f067ec --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,3 @@ +# ReScript MCP Server + +A Model Context Protocol (MCP) server that provides comprehensive ReScript language support. diff --git a/mcp/TODO.md b/mcp/TODO.md new file mode 100644 index 0000000000..7806a92b1f --- /dev/null +++ b/mcp/TODO.md @@ -0,0 +1,5 @@ +## Tools to implement + +- [ ] Lookup location. +- [ ] Lookup path (`SomeModule.whatever`). Needs to handle both types and values. +- [ ] Give path to `md` with docs/types for file/lib so the LLM can grep. diff --git a/mcp/package.json b/mcp/package.json new file mode 100644 index 0000000000..0bed23dc7f --- /dev/null +++ b/mcp/package.json @@ -0,0 +1,16 @@ +{ + "name": "rescript-mcp", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/server.js", + "dev": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.12.1", + "@types/node": "^22.0.0", + "typescript": "5.8.3" + } +} diff --git a/mcp/server.ts b/mcp/server.ts new file mode 100644 index 0000000000..80f67c4ed4 --- /dev/null +++ b/mcp/server.ts @@ -0,0 +1,174 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { execFileSync, ExecFileSyncOptions } from "child_process"; +import * as fs from "fs"; +import * as path from "path"; + +// Create an MCP server +const server = new McpServer({ + name: "ReScript MCP", + version: "1.0.0", +}); + +const { + binPaths: { rescript_editor_analysis_exe, rescript_tools_exe }, +} = await import(`@rescript/${process.platform}-${process.arch}`); + +function execAnalysis(args: string[]): string | null { + let options: ExecFileSyncOptions = { + maxBuffer: Infinity, + env: { + // TODO: Do not hard code + RESCRIPT_VERSION: "12.0.0-alpha.14", + }, + }; + + let stdout = ""; + try { + stdout = execFileSync( + rescript_editor_analysis_exe, + args, + options + ).toString(); + return stdout; + } catch (e) { + console.error(e); + } + + return null; +} + +// Add type definition finder tool +server.tool( + "find_type_definition", + { + filePath: z.string().describe("Absolute path to the ReScript file"), + line: z.number().describe("Line number (0-indexed)"), + col: z.number().describe("Column number (0-indexed)"), + }, + { + destructiveHint: false, + idempotentHint: true, + readOnlyHint: true, + title: "Finds the type definition of a symbol at the given location", + }, + ({ filePath, line, col }) => { + try { + const result = execAnalysis([ + "mcp", + "loc-info", + filePath, + line.toString(), + col.toString(), + ]); + return { + content: [{ type: "text", text: result ?? "No result." }], + }; + } catch (error) { + return { + content: [ + { type: "text", text: `Error finding type definition: ${error}` }, + ], + }; + } + } +); + +server.tool( + "find_type_of_global_identifier", + { + filePath: z.string().describe("Absolute path to the ReScript file"), + identifier: z.string().describe("The identifier to find the type of"), + }, + { + destructiveHint: false, + idempotentHint: true, + readOnlyHint: true, + title: `Finds the type of a global identifier, like \`SomeModule.SomeNestedModule.someValue\` or \`React.createElement\`. + + - **Always** use the fully qualified global path. Even if you are in \`SomeModule.res\` and are looking for \`x\` in that file, use the fully qualified path \`SomeModule.x\`.`, + }, + ({ filePath, identifier }) => { + try { + const result = execAnalysis([ + "mcp", + "identifier-info", + filePath, + identifier, + ]); + return { + content: [{ type: "text", text: result ?? "No result." }], + }; + } catch (error) { + return { + content: [ + { type: "text", text: `Error finding type definition: ${error}` }, + ], + }; + } + } +); + +server.tool( + "language_docs_file_path", + {}, + { + destructiveHint: false, + idempotentHint: true, + readOnlyHint: true, + title: `Gets the path to a markdown file containing the full language docs for ReScript the language, including examples. + + - **Always** use this to grep or search as needed when you have questions about ReScript the language.`, + }, + async () => { + try { + const cacheDir = path.resolve(__dirname, ".cache"); + const docsFileName = "rescript-lang-docs-12.0.0.md"; + const docsFilePath = path.join(cacheDir, docsFileName); + + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + if (!fs.existsSync(docsFilePath)) { + const res = await fetch( + "https://rescript-lang.org/llms/manual/v12.0.0/llm-full.txt" + ); + const text = await res.text(); + fs.writeFileSync(docsFilePath, text); + } + + return { + content: [{ type: "text", text: docsFilePath }], + }; + } catch (e) { + return { + content: [ + { type: "text", text: `Error getting language docs file path: ${e}` }, + ], + }; + } + } +); + +// Main function to start the server +async function main() { + try { + // Clear the cache directory + fs.rmSync(path.resolve(__dirname, ".cache"), { + recursive: true, + force: true, + }); + + // Start receiving messages on stdin and sending messages on stdout + const transport = new StdioServerTransport(); + await server.connect(transport); + } catch (error) { + console.error("Failed to start MCP server:", error); + process.exit(1); + } +} + +// Start the server +main(); diff --git a/mcp/tsconfig.json b/mcp/tsconfig.json new file mode 100644 index 0000000000..550f306233 --- /dev/null +++ b/mcp/tsconfig.json @@ -0,0 +1,114 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2022", + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ESNext", + "moduleResolution": "node", + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, + "declarationMap": true, + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + "allowSyntheticDefaultImports": true, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, + + /* Type Checking */ + "strict": true, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"], + "rootDir": "./" +} diff --git a/mcp/yarn.lock b/mcp/yarn.lock new file mode 100644 index 0000000000..879fc2e723 --- /dev/null +++ b/mcp/yarn.lock @@ -0,0 +1,860 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@modelcontextprotocol/sdk@npm:1.12.1": + version: 1.12.1 + resolution: "@modelcontextprotocol/sdk@npm:1.12.1" + dependencies: + ajv: "npm:^6.12.6" + content-type: "npm:^1.0.5" + cors: "npm:^2.8.5" + cross-spawn: "npm:^7.0.5" + eventsource: "npm:^3.0.2" + express: "npm:^5.0.1" + express-rate-limit: "npm:^7.5.0" + pkce-challenge: "npm:^5.0.0" + raw-body: "npm:^3.0.0" + zod: "npm:^3.23.8" + zod-to-json-schema: "npm:^3.24.1" + checksum: 10c0/19daf4bc01373a8bd816faa6e8b139c20a56ae4c9cf25c6e900fab443b34e44bcf699a61612cf421c6480d803230003576b12823a04804dc71d7007f530677ac + languageName: node + linkType: hard + +"@types/node@npm:^22.0.0": + version: 22.15.32 + resolution: "@types/node@npm:22.15.32" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/63a2fa52adf1134d1b3bee8b1862d4b8e4550fffc190551068d3d41a41d9e5c0c8f1cb81faa18767b260637360f662115c26c5e4e7718868ead40c4a57cbc0e3 + languageName: node + linkType: hard + +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef + languageName: node + linkType: hard + +"ajv@npm:^6.12.6": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"body-parser@npm:^2.2.0": + version: 2.2.0 + resolution: "body-parser@npm:2.2.0" + dependencies: + bytes: "npm:^3.1.2" + content-type: "npm:^1.0.5" + debug: "npm:^4.4.0" + http-errors: "npm:^2.0.0" + iconv-lite: "npm:^0.6.3" + on-finished: "npm:^2.4.1" + qs: "npm:^6.14.0" + raw-body: "npm:^3.0.0" + type-is: "npm:^2.0.0" + checksum: 10c0/a9ded39e71ac9668e2211afa72e82ff86cc5ef94de1250b7d1ba9cc299e4150408aaa5f1e8b03dd4578472a3ce6d1caa2a23b27a6c18e526e48b4595174c116c + languageName: node + linkType: hard + +"bytes@npm:3.1.2, bytes@npm:^3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"content-disposition@npm:^1.0.0": + version: 1.0.0 + resolution: "content-disposition@npm:1.0.0" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27 + languageName: node + linkType: hard + +"content-type@npm:^1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6 + languageName: node + linkType: hard + +"cookie@npm:^0.7.1": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 + languageName: node + linkType: hard + +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.5": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"debug@npm:^4.3.5, debug@npm:^4.4.0": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 + languageName: node + linkType: hard + +"depd@npm:2.0.0, depd@npm:^2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"encodeurl@npm:^2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"escape-html@npm:^1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"etag@npm:^1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"eventsource-parser@npm:^3.0.1": + version: 3.0.2 + resolution: "eventsource-parser@npm:3.0.2" + checksum: 10c0/067c6e60b7c68a4577630cc7e11d2aaeef52005e377a213308c7c2350596a175d5a179671d85f570726dce3f451c15d174ece4479ce68a1805686c88950d08dd + languageName: node + linkType: hard + +"eventsource@npm:^3.0.2": + version: 3.0.7 + resolution: "eventsource@npm:3.0.7" + dependencies: + eventsource-parser: "npm:^3.0.1" + checksum: 10c0/c48a73c38f300e33e9f11375d4ee969f25cbb0519608a12378a38068055ae8b55b6e0e8a49c3f91c784068434efe1d9f01eb49b6315b04b0da9157879ce2f67d + languageName: node + linkType: hard + +"express-rate-limit@npm:^7.5.0": + version: 7.5.0 + resolution: "express-rate-limit@npm:7.5.0" + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + checksum: 10c0/3e96afa05b4f577395688ede37e0cb19901f20c350b32575fb076f3d25176209fb88d3648151755c232aaf304147c58531f070757978f376e2f08326449299fd + languageName: node + linkType: hard + +"express@npm:^5.0.1": + version: 5.1.0 + resolution: "express@npm:5.1.0" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.2.0" + content-disposition: "npm:^1.0.0" + content-type: "npm:^1.0.5" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.7" + qs: "npm:^6.14.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10c0/80ce7c53c5f56887d759b94c3f2283e2e51066c98d4b72a4cc1338e832b77f1e54f30d0239cc10815a0f849bdb753e6a284d2fa48d4ab56faf9c501f55d751d6 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"finalhandler@npm:^2.1.0": + version: 2.1.0 + resolution: "finalhandler@npm:2.1.0" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10c0/da0bbca6d03873472ee890564eb2183f4ed377f25f3628a0fc9d16dac40bed7b150a0d82ebb77356e4c6d97d2796ad2dba22948b951dddee2c8768b0d1b9fb1f + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"inherits@npm:2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"is-promise@npm:^4.0.0": + version: 4.0.0 + resolution: "is-promise@npm:4.0.0" + checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"media-typer@npm:^1.1.0": + version: 1.1.0 + resolution: "media-typer@npm:1.1.0" + checksum: 10c0/7b4baa40b25964bb90e2121ee489ec38642127e48d0cc2b6baa442688d3fde6262bfdca86d6bbf6ba708784afcac168c06840c71facac70e390f5f759ac121b9 + languageName: node + linkType: hard + +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 + languageName: node + linkType: hard + +"mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 + languageName: node + linkType: hard + +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": + version: 3.0.1 + resolution: "mime-types@npm:3.0.1" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10c0/bd8c20d3694548089cf229016124f8f40e6a60bbb600161ae13e45f793a2d5bb40f96bbc61f275836696179c77c1d6bf4967b2a75e0a8ad40fe31f4ed5be4da5 + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"object-assign@npm:^4": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"on-finished@npm:^2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"parseurl@npm:^1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-to-regexp@npm:^8.0.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a + languageName: node + linkType: hard + +"pkce-challenge@npm:^5.0.0": + version: 5.0.0 + resolution: "pkce-challenge@npm:5.0.0" + checksum: 10c0/c6706d627fdbb6f22bf8cc5d60d96d6b6a7bb481399b336a3d3f4e9bfba3e167a2c32f8ec0b5e74be686a0ba3bcc9894865d4c2dd1b91cea4c05dba1f28602c3 + languageName: node + linkType: hard + +"proxy-addr@npm:^2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"qs@npm:^6.14.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c + languageName: node + linkType: hard + +"range-parser@npm:^1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:^3.0.0": + version: 3.0.0 + resolution: "raw-body@npm:3.0.0" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.6.3" + unpipe: "npm:1.0.0" + checksum: 10c0/f8daf4b724064a4811d118745a781ca0fb4676298b8adadfd6591155549cfea0a067523cf7dd3baeb1265fecc9ce5dfb2fc788c12c66b85202a336593ece0f87 + languageName: node + linkType: hard + +"rescript-mcp@workspace:.": + version: 0.0.0-use.local + resolution: "rescript-mcp@workspace:." + dependencies: + "@modelcontextprotocol/sdk": "npm:1.12.1" + "@types/node": "npm:^22.0.0" + typescript: "npm:5.8.3" + languageName: unknown + linkType: soft + +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" + dependencies: + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10c0/3279de7450c8eae2f6e095e9edacbdeec0abb5cb7249c6e719faa0db2dba43574b4fff5892d9220631c9abaff52dd3cad648cfea2aaace845e1a071915ac8867 + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.0 + resolution: "send@npm:1.2.0" + dependencies: + debug: "npm:^4.3.5" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + mime-types: "npm:^3.0.1" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.1" + checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 + languageName: node + linkType: hard + +"serve-static@npm:^2.2.0": + version: 2.2.0 + resolution: "serve-static@npm:2.2.0" + dependencies: + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"statuses@npm:^2.0.1": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"type-is@npm:^2.0.0, type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" + dependencies: + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10c0/7f7ec0a060b16880bdad36824ab37c26019454b67d73e8a465ed5a3587440fbe158bc765f0da68344498235c877e7dbbb1600beccc94628ed05599d667951b99 + languageName: node + linkType: hard + +"typescript@npm:5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.8.3#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"unpipe@npm:1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"vary@npm:^1, vary@npm:^1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"zod-to-json-schema@npm:^3.24.1": + version: 3.24.5 + resolution: "zod-to-json-schema@npm:3.24.5" + peerDependencies: + zod: ^3.24.1 + checksum: 10c0/0745b94ba53e652d39f262641cdeb2f75d24339fb6076a38ce55bcf53d82dfaea63adf524ebc5f658681005401687f8e9551c4feca7c4c882e123e66091dfb90 + languageName: node + linkType: hard + +"zod@npm:^3.23.8": + version: 3.25.61 + resolution: "zod@npm:3.25.61" + checksum: 10c0/45386e88c52b5946dbe22c95fe45f3e7c1c90c4fba8cd7b8ce4e6cf0a79c987026990cbb082c54bb2926be35ed49947f351f05b35d6507aa17c23c088a5f4667 + languageName: node + linkType: hard diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res index 8a099f432e..13b1136e82 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res @@ -8,7 +8,7 @@ let resultGetExn = r => r->Result.getExn let nullGetExn = n => n->Null.getExn @raises(JsExn) -let bigIntFromStringExn = s => s->BigInt.fromStringOrThrow +let bigIntFromStringExn = s => s->BigInt.fromStringExn @raises(JsExn) -let jsonParseExn = s => s->JSON.parseOrThrow +let jsonParseExn = s => s->JSON.parseExn diff --git a/tests/analysis_tests/tests/src/MCP.res b/tests/analysis_tests/tests/src/MCP.res new file mode 100644 index 0000000000..c2d1db090a --- /dev/null +++ b/tests/analysis_tests/tests/src/MCP.res @@ -0,0 +1,36 @@ +module TestType = { + type first = { + one: bool, + two?: string, + } + + type variant = + | One + | Two(string) + | Three(bool) + + type second = { + first: first, + variant: variant, + } +} + +let xx = { + TestType.first: { + one: true, + }, + variant: One, +} + +type xx = Hello + +let ft = () => xx + +let ff = ft() +// ^mli + +// ^mif MCP.xx +// ^mif Rxjs.Subscriber.t + +// ^mdp src/MCPDocs.res +// ^mdl @rescript/react diff --git a/tests/analysis_tests/tests/src/MCPDocs.res b/tests/analysis_tests/tests/src/MCPDocs.res new file mode 100644 index 0000000000..77a0cdecaf --- /dev/null +++ b/tests/analysis_tests/tests/src/MCPDocs.res @@ -0,0 +1,8 @@ +type x = {test: bool} + +module SomeModule = { + let ff = {test: true} + + @unboxed + type someVariant = One(int) | Two(string) +} diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index 412e810c27..ed71ec31d9 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -802,7 +802,7 @@ Path s "kind": 12, "tags": [], "detail": "(string, RegExp.t, ~limit: int) => array>", - "documentation": {"kind": "markdown", "value": "\n`splitByRegExpAtMost(str, regexp, ~limit)` splits the given `str` at every\noccurrence of `regexp` and returns an array of the first `limit` resulting\nsubstrings. If `limit` is negative or greater than the number of substrings, the\narray will contain all the substrings.\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) on MDN.\n\n## Examples\n\n```rescript\nString.splitByRegExpAtMost(\"Hello World. How are you doing?\", / /, ~limit=3) ==\n [Some(\"Hello\"), Some(\"World.\"), Some(\"How\")]\n```\n"}, + "documentation": {"kind": "markdown", "value": "\n`splitByRegExpAtMost(str, regexp, ~limit)` splits the given `str` at every\noccurrence of `regexp` and returns an array of the first `limit` resulting\nsubstrings. If `limit` is negative or greater than the number of substrings, the\narray will contain all the substrings.\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) on MDN.\n\n## Examples\n\n```rescript\nString.splitByRegExpAtMost(\"Hello World. How are you doing?\", %re(\"/ /\"), ~limit=3) == [\n Some(\"Hello\"),\n Some(\"World.\"),\n Some(\"How\"),\n]\n```\n"}, "sortText": "splitByRegExpAtMost", "insertText": "->String.splitByRegExpAtMost", "additionalTextEdits": [{ diff --git a/tests/analysis_tests/tests/src/expected/Jsx2.res.txt b/tests/analysis_tests/tests/src/expected/Jsx2.res.txt index 66920cb633..8a045b50fc 100644 --- a/tests/analysis_tests/tests/src/expected/Jsx2.res.txt +++ b/tests/analysis_tests/tests/src/expected/Jsx2.res.txt @@ -58,6 +58,26 @@ Path M "tags": [], "detail": "module Map", "documentation": null + }, { + "label": "MCP", + "kind": 9, + "tags": [], + "detail": "module MCP", + "documentation": null, + "data": { + "modulePath": "MCP", + "filePath": "src/Jsx2.res" + } + }, { + "label": "MCPDocs", + "kind": 9, + "tags": [], + "detail": "module MCPDocs", + "documentation": null, + "data": { + "modulePath": "MCPDocs", + "filePath": "src/Jsx2.res" + } }, { "label": "ModuleStuff", "kind": 9, diff --git a/tests/analysis_tests/tests/src/expected/MCP.res.txt b/tests/analysis_tests/tests/src/expected/MCP.res.txt new file mode 100644 index 0000000000..bd5e5c8b93 --- /dev/null +++ b/tests/analysis_tests/tests/src/expected/MCP.res.txt @@ -0,0 +1,109 @@ +MCP loc info src/MCP.res 28:4 + +TestType.second + +type second = {first: first, variant: variant} + + + +type first = {one: bool, two?: string} + +type variant = One | Two(string) | Three(bool) + + +MCP identifier info src/MCP.res 30:3 +Value MCP.xx: + + +TestType.second + +type second = {first: first, variant: variant} + + + +type first = {one: bool, two?: string} + +type variant = One | Two(string) | Three(bool) + + + +Type MCP.xx: + +type xx = Hello + + +MCP identifier info src/MCP.res 31:3 +Type Rxjs.Subscriber.t: + +type t<'t> = {next: 't => unit} + + +MCP docs for project file +# MCPDocs + +**KIND:** MODULE + +**SOURCE:** src/MCPDocs.res:1:1 + +## TYPES + +### x + +**KIND:** TYPE + +**SIGNATURE:** `type x = {test: bool}` + +**SOURCE:** src/MCPDocs.res:1:1 + + +**RECORD_FIELDS:** +- **FIELD:** `test` + - **TYPE:** `bool` + - **OPTIONAL:** false + + + +## MODULES + +### SomeModule + +**KIND:** MODULE + +**SOURCE:** src/MCPDocs.res:3:8 + +**MODULE_CONTENTS:** + +#### ff + +**KIND:** VALUE + +**SIGNATURE:** `let ff: x` + +**SOURCE:** src/MCPDocs.res:4:7 + + + +#### someVariant + +**KIND:** TYPE + +**SIGNATURE:** `@unboxed type someVariant = One(int) | Two(string)` + +**SOURCE:** src/MCPDocs.res:7:3 + + +**VARIANT_CONSTRUCTORS:** +- **CONSTRUCTOR:** `One` + - **SIGNATURE:** `One(int)` + +- **CONSTRUCTOR:** `Two` + - **SIGNATURE:** `Two(string)` + + + + + + +MCP docs for library +TODO + diff --git a/tests/analysis_tests/tests/src/expected/MCPDocs.res.txt b/tests/analysis_tests/tests/src/expected/MCPDocs.res.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/bin/main.ml b/tools/bin/main.ml index 2d97dea930..2cdb8ff15a 100644 --- a/tools/bin/main.ml +++ b/tools/bin/main.ml @@ -41,7 +41,8 @@ let main () = | Some "true" -> Analysis.Cfg.isDocGenFromCompiler := true | _ -> () in - logAndExit (Tools.extractDocs ~entryPointFile:path ~debug:false) + logAndExit + (Analysis.DocGen.extractDocsToJson ~entryPointFile:path ~debug:false) | _ -> logAndExit (Error docHelp)) | "reanalyze" :: _ -> let len = Array.length Sys.argv in diff --git a/tools/src/tools.ml b/tools/src/tools.ml index 2db1be7e6a..7889996359 100644 --- a/tools/src/tools.ml +++ b/tools/src/tools.ml @@ -1,643 +1,3 @@ -open Analysis - -module StringSet = Set.Make (String) - -type fieldDoc = { - fieldName: string; - docstrings: string list; - signature: string; - optional: bool; - deprecated: string option; -} - -type constructorPayload = InlineRecord of {fieldDocs: fieldDoc list} - -type constructorDoc = { - constructorName: string; - docstrings: string list; - signature: string; - deprecated: string option; - items: constructorPayload option; -} - -type typeDoc = {path: string; genericParameters: typeDoc list} -type valueSignature = {parameters: typeDoc list; returnType: typeDoc} - -type source = {filepath: string; line: int; col: int} - -type docItemDetail = - | Record of {fieldDocs: fieldDoc list} - | Variant of {constructorDocs: constructorDoc list} - | Signature of valueSignature - -type docItem = - | Value of { - id: string; - docstring: string list; - signature: string; - name: string; - deprecated: string option; - detail: docItemDetail option; - source: source; - } - | Type of { - id: string; - docstring: string list; - signature: string; - name: string; - deprecated: string option; - detail: docItemDetail option; - source: source; - (** Additional documentation for constructors and record fields, if available. *) - } - | Module of docsForModule - | ModuleType of { - id: string; - docstring: string list; - deprecated: string option; - name: string; - source: source; - items: docItem list; - } - | ModuleAlias of { - id: string; - docstring: string list; - name: string; - source: source; - items: docItem list; - } -and docsForModule = { - id: string; - docstring: string list; - deprecated: string option; - name: string; - moduletypeid: string option; - source: source; - items: docItem list; -} - -let stringifyDocstrings docstrings = - let open Protocol in - docstrings - |> List.map (fun docstring -> docstring |> String.trim |> wrapInQuotes) - |> array - -let stringifyFieldDoc ~indentation (fieldDoc : fieldDoc) = - let open Protocol in - stringifyObject ~indentation:(indentation + 1) - [ - ("name", Some (wrapInQuotes fieldDoc.fieldName)); - ( "deprecated", - match fieldDoc.deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ("optional", Some (string_of_bool fieldDoc.optional)); - ("docstrings", Some (stringifyDocstrings fieldDoc.docstrings)); - ("signature", Some (wrapInQuotes fieldDoc.signature)); - ] - -let stringifyConstructorPayload ~indentation - (constructorPayload : constructorPayload) = - let open Protocol in - match constructorPayload with - | InlineRecord {fieldDocs} -> - stringifyObject ~indentation:(indentation + 1) - [ - ("kind", Some (wrapInQuotes "inlineRecord")); - ( "fields", - Some - (fieldDocs - |> List.map (stringifyFieldDoc ~indentation:(indentation + 1)) - |> array) ); - ] - -let rec stringifyTypeDoc ~indentation (td : typeDoc) : string = - let open Protocol in - let ps = - match td.genericParameters with - | [] -> None - | ts -> - ts |> List.map (stringifyTypeDoc ~indentation:(indentation + 1)) - |> fun ts -> Some (array ts) - in - - stringifyObject ~indentation:(indentation + 1) - [("path", Some (wrapInQuotes td.path)); ("genericTypeParameters", ps)] - -let stringifyDetail ?(indentation = 0) (detail : docItemDetail) = - let open Protocol in - match detail with - | Record {fieldDocs} -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("kind", Some (wrapInQuotes "record")); - ( "items", - Some (fieldDocs |> List.map (stringifyFieldDoc ~indentation) |> array) - ); - ] - | Variant {constructorDocs} -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("kind", Some (wrapInQuotes "variant")); - ( "items", - Some - (constructorDocs - |> List.map (fun constructorDoc -> - stringifyObject ~startOnNewline:true - ~indentation:(indentation + 1) - [ - ( "name", - Some (wrapInQuotes constructorDoc.constructorName) ); - ( "deprecated", - match constructorDoc.deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ( "docstrings", - Some (stringifyDocstrings constructorDoc.docstrings) ); - ( "signature", - Some (wrapInQuotes constructorDoc.signature) ); - ( "payload", - match constructorDoc.items with - | None -> None - | Some constructorPayload -> - Some - (stringifyConstructorPayload - ~indentation:(indentation + 1) - constructorPayload) ); - ]) - |> array) ); - ] - | Signature {parameters; returnType} -> - let ps = - match parameters with - | [] -> None - | ps -> - ps |> List.map (stringifyTypeDoc ~indentation:(indentation + 1)) - |> fun ps -> Some (array ps) - in - stringifyObject ~startOnNewline:true ~indentation - [ - ("kind", Some (wrapInQuotes "signature")); - ( "details", - Some - (stringifyObject ~startOnNewline:false ~indentation - [ - ("parameters", ps); - ("returnType", Some (stringifyTypeDoc ~indentation returnType)); - ]) ); - ] - -let stringifySource ~indentation source = - let open Protocol in - stringifyObject ~startOnNewline:false ~indentation - [ - ("filepath", Some (source.filepath |> wrapInQuotes)); - ("line", Some (source.line |> string_of_int)); - ("col", Some (source.col |> string_of_int)); - ] - -let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) = - let open Protocol in - match item with - | Value {id; docstring; signature; name; deprecated; source; detail} -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("id", Some (wrapInQuotes id)); - ("kind", Some (wrapInQuotes "value")); - ("name", Some (name |> wrapInQuotes)); - ( "deprecated", - match deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ("signature", Some (signature |> String.trim |> wrapInQuotes)); - ("docstrings", Some (stringifyDocstrings docstring)); - ("source", Some (stringifySource ~indentation:(indentation + 1) source)); - ( "detail", - match detail with - | None -> None - | Some detail -> - Some (stringifyDetail ~indentation:(indentation + 1) detail) ); - ] - | Type {id; docstring; signature; name; deprecated; detail; source} -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("id", Some (wrapInQuotes id)); - ("kind", Some (wrapInQuotes "type")); - ("name", Some (name |> wrapInQuotes)); - ( "deprecated", - match deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ("signature", Some (signature |> wrapInQuotes)); - ("docstrings", Some (stringifyDocstrings docstring)); - ("source", Some (stringifySource ~indentation:(indentation + 1) source)); - ( "detail", - match detail with - | None -> None - | Some detail -> - Some (stringifyDetail ~indentation:(indentation + 1) detail) ); - ] - | Module m -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("id", Some (wrapInQuotes m.id)); - ("name", Some (wrapInQuotes m.name)); - ("kind", Some (wrapInQuotes "module")); - ( "deprecated", - match m.deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ( "moduletypeid", - match m.moduletypeid with - | Some path -> Some (wrapInQuotes path) - | None -> None ); - ("docstrings", Some (stringifyDocstrings m.docstring)); - ( "source", - Some (stringifySource ~indentation:(indentation + 1) m.source) ); - ( "items", - Some - (m.items - |> List.map - (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) - |> array) ); - ] - | ModuleType m -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("id", Some (wrapInQuotes m.id)); - ("name", Some (wrapInQuotes m.name)); - ("kind", Some (wrapInQuotes "moduleType")); - ( "deprecated", - match m.deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ("docstrings", Some (stringifyDocstrings m.docstring)); - ( "source", - Some (stringifySource ~indentation:(indentation + 1) m.source) ); - ( "items", - Some - (m.items - |> List.map - (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) - |> array) ); - ] - | ModuleAlias m -> - stringifyObject ~startOnNewline:true ~indentation - [ - ("id", Some (wrapInQuotes m.id)); - ("kind", Some (wrapInQuotes "moduleAlias")); - ("name", Some (wrapInQuotes m.name)); - ("docstrings", Some (stringifyDocstrings m.docstring)); - ( "source", - Some (stringifySource ~indentation:(indentation + 1) m.source) ); - ( "items", - Some - (m.items - |> List.map - (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) - |> array) ); - ] - -and stringifyDocsForModule ?(indentation = 0) ~originalEnv (d : docsForModule) = - let open Protocol in - stringifyObject ~startOnNewline:true ~indentation - [ - ("name", Some (wrapInQuotes d.name)); - ( "deprecated", - match d.deprecated with - | Some d -> Some (wrapInQuotes d) - | None -> None ); - ("docstrings", Some (stringifyDocstrings d.docstring)); - ("source", Some (stringifySource ~indentation:(indentation + 1) d.source)); - ( "items", - Some - (d.items - |> List.map - (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) - |> array) ); - ] - -let fieldToFieldDoc (field : SharedTypes.field) : fieldDoc = - { - fieldName = field.fname.txt; - docstrings = field.docstring; - optional = field.optional; - signature = Shared.typeToString field.typ; - deprecated = field.deprecated; - } - -let typeDetail typ ~env ~full = - let open SharedTypes in - match TypeUtils.extractTypeFromResolvedType ~env ~full typ with - | Some (Trecord {fields}) -> - Some (Record {fieldDocs = fields |> List.map fieldToFieldDoc}) - | Some (Tvariant {constructors}) -> - Some - (Variant - { - constructorDocs = - constructors - |> List.map (fun (c : Constructor.t) -> - { - constructorName = c.cname.txt; - docstrings = c.docstring; - signature = CompletionBackEnd.showConstructor c; - deprecated = c.deprecated; - items = - (match c.args with - | InlineRecord fields -> - Some - (InlineRecord - {fieldDocs = fields |> List.map fieldToFieldDoc}) - | _ -> None); - }); - }) - | _ -> None - -(* split a list into two parts all the items except the last one and the last item *) -let splitLast l = - let rec splitLast' acc = function - | [] -> failwith "splitLast: empty list" - | [x] -> (List.rev acc, x) - | x :: xs -> splitLast' (x :: acc) xs - in - splitLast' [] l - -let path_to_string path = - let buf = Buffer.create 64 in - let rec aux = function - | Path.Pident id -> Buffer.add_string buf (Ident.name id) - | Path.Pdot (p, s, _) -> - aux p; - Buffer.add_char buf '.'; - Buffer.add_string buf s - | Path.Papply (p1, p2) -> - aux p1; - Buffer.add_char buf '('; - aux p2; - Buffer.add_char buf ')' - in - aux path; - Buffer.contents buf - -let valueDetail (typ : Types.type_expr) = - let rec collectSignatureTypes (typ : Types.type_expr) = - match typ.desc with - | Tlink t | Tsubst t | Tpoly (t, []) -> collectSignatureTypes t - | Tconstr (path, ts, _) -> ( - let p = path_to_string path in - match ts with - | [] -> [{path = p; genericParameters = []}] - | ts -> - let ts = - ts - |> List.concat_map (fun (t : Types.type_expr) -> - collectSignatureTypes t) - in - [{path = p; genericParameters = ts}]) - | Tarrow (_, t1, t2, _, _) -> - collectSignatureTypes t1 @ collectSignatureTypes t2 - | Tvar None -> [{path = "_"; genericParameters = []}] - | _ -> [] - in - match collectSignatureTypes typ with - | [] -> None - | ts -> - let parameters, returnType = splitLast ts in - Some (Signature {parameters; returnType}) - -let makeId modulePath ~identifier = - identifier :: modulePath |> List.rev |> SharedTypes.ident - -let getSource ~rootPath ({loc_start} : Location.t) = - let line, col = Pos.ofLexing loc_start in - let filepath = - Files.relpath rootPath loc_start.pos_fname - |> Files.split Filename.dir_sep - |> String.concat "/" - in - {filepath; line = line + 1; col = col + 1} - -let extractDocs ~entryPointFile ~debug = - let path = - match Filename.is_relative entryPointFile with - | true -> Unix.realpath entryPointFile - | false -> entryPointFile - in - if debug then Printf.printf "extracting docs for %s\n" path; - let result = - match - FindFiles.isImplementation path = false - && FindFiles.isInterface path = false - with - | false -> ( - let path = - if FindFiles.isImplementation path then - let pathAsResi = - (path |> Filename.dirname) ^ "/" - ^ (path |> Filename.basename |> Filename.chop_extension) - ^ ".resi" - in - if Sys.file_exists pathAsResi then ( - if debug then - Printf.printf "preferring found resi file for impl: %s\n" - pathAsResi; - pathAsResi) - else path - else path - in - match Cmt.loadFullCmtFromPath ~path with - | None -> - Error - (Printf.sprintf - "error: failed to generate doc for %s, try to build the project" - path) - | Some full -> - let file = full.file in - let structure = file.structure in - let rootPath = full.package.rootPath in - let open SharedTypes in - let env = QueryEnv.fromFile file in - let rec extractDocsForModule ?(modulePath = [env.file.moduleName]) - (structure : Module.structure) = - let valuesSeen = ref StringSet.empty in - { - id = modulePath |> List.rev |> ident; - docstring = structure.docstring |> List.map String.trim; - name = structure.name; - moduletypeid = None; - deprecated = structure.deprecated; - source = - { - filepath = - (match rootPath = "." with - | true -> file.uri |> Uri.toPath - | false -> - Files.relpath rootPath (file.uri |> Uri.toPath) - |> Files.split Filename.dir_sep - |> String.concat "/"); - line = 1; - col = 1; - }; - items = - structure.items - |> List.filter_map (fun (item : Module.item) -> - let item = - { - item with - name = Ext_ident.unwrap_uppercase_exotic item.name; - } - in - let source = getSource ~rootPath item.loc in - match item.kind with - | Value typ -> - Some - (Value - { - id = modulePath |> makeId ~identifier:item.name; - docstring = item.docstring |> List.map String.trim; - signature = - "let " ^ item.name ^ ": " - ^ Shared.typeToString typ; - name = item.name; - deprecated = item.deprecated; - detail = valueDetail typ; - source; - }) - | Type (typ, _) -> - Some - (Type - { - id = modulePath |> makeId ~identifier:item.name; - docstring = item.docstring |> List.map String.trim; - signature = - typ.decl |> Shared.declToString item.name; - name = item.name; - deprecated = item.deprecated; - detail = typeDetail typ ~full ~env; - source; - }) - | Module {type_ = Ident p; isModuleType = false} -> - (* module Whatever = OtherModule *) - let aliasToModule = p |> pathIdentToString in - let id = - (modulePath |> List.rev |> List.hd) ^ "." ^ item.name - in - let items, internalDocstrings = - match - ProcessCmt.fileForModule ~package:full.package - aliasToModule - with - | None -> ([], []) - | Some file -> - let docs = - extractDocsForModule ~modulePath:[id] - file.structure - in - (docs.items, docs.docstring) - in - Some - (ModuleAlias - { - id; - name = item.name; - source; - items; - docstring = - item.docstring @ internalDocstrings - |> List.map String.trim; - }) - | Module {type_ = Structure m; isModuleType = false} -> - (* module Whatever = {} in res or module Whatever: {} in resi. *) - let modulePath = m.name :: modulePath in - let docs = extractDocsForModule ~modulePath m in - Some - (Module - { - id = modulePath |> List.rev |> ident; - name = m.name; - moduletypeid = None; - docstring = item.docstring @ m.docstring; - deprecated = item.deprecated; - source; - items = docs.items; - }) - | Module {type_ = Structure m; isModuleType = true} -> - (* module type Whatever = {} *) - let modulePath = m.name :: modulePath in - let docs = extractDocsForModule ~modulePath m in - Some - (ModuleType - { - id = modulePath |> List.rev |> ident; - name = m.name; - docstring = item.docstring @ m.docstring; - deprecated = item.deprecated; - source; - items = docs.items; - }) - | Module - { - type_ = - Constraint (Structure _impl, Structure interface); - } -> - (* module Whatever: { } = { }. Prefer the interface. *) - Some - (Module - (extractDocsForModule - ~modulePath:(interface.name :: modulePath) - interface)) - | Module {type_ = Constraint (Structure m, Ident p)} -> - (* module M: T = { }. Print M *) - let docs = - extractDocsForModule ~modulePath:(m.name :: modulePath) - m - in - let identModulePath = p |> Path.head |> Ident.name in - - let moduleTypeIdPath = - match - ProcessCmt.fileForModule ~package:full.package - identModulePath - |> Option.is_none - with - | false -> [] - | true -> [modulePath |> List.rev |> List.hd] - in - - Some - (Module - { - docs with - moduletypeid = - Some - (makeId ~identifier:(Path.name p) - moduleTypeIdPath); - }) - | _ -> None) - (* Filter out shadowed bindings by keeping only the last value associated with an id *) - |> List.rev - |> List.filter_map (fun (docItem : docItem) -> - match docItem with - | Value {id} -> - if StringSet.mem id !valuesSeen then None - else ( - valuesSeen := StringSet.add id !valuesSeen; - Some docItem) - | _ -> Some docItem) - |> List.rev; - } - in - let docs = extractDocsForModule structure in - Ok (stringifyDocsForModule ~originalEnv:env docs)) - | true -> - Error - (Printf.sprintf - "error: failed to read %s, expected an .res or .resi file" path) - in - - result - let extractEmbedded ~extensionPoints ~filename = let {Res_driver.parsetree = structure} = Res_driver.parsing_engine.parse_implementation ~for_printer:false ~filename