diff --git a/analysis/src/Cli.ml b/analysis/src/Cli.ml index 87765c755..a7529b705 100644 --- a/analysis/src/Cli.ml +++ b/analysis/src/Cli.ml @@ -120,6 +120,7 @@ let main () = ~pos:(int_of_string line_start, int_of_string line_end) ~maxLength ~debug:false | [_; "codeLens"; path] -> Commands.codeLens ~path ~debug:false + | [_; "extractDocs"; path] -> DocExtraction.extractDocs ~path ~debug:false | [_; "codeAction"; path; startLine; startCol; endLine; endCol; currentFile] -> Commands.codeAction ~path diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index c46225fa0..486f46743 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -345,6 +345,9 @@ let test ~path = let currentFile = createCurrentFile () in signatureHelp ~path ~pos:(line, col) ~currentFile ~debug:true; Sys.remove currentFile + | "dex" -> + print_endline ("Documentation extraction " ^ path); + DocExtraction.extractDocs ~path ~debug:true | "int" -> print_endline ("Create Interface " ^ path); let cmiFile = diff --git a/analysis/src/DocExtraction.ml b/analysis/src/DocExtraction.ml new file mode 100644 index 000000000..e1ca613bc --- /dev/null +++ b/analysis/src/DocExtraction.ml @@ -0,0 +1,347 @@ +type fieldDoc = { + fieldName: string; + docstrings: string list; + signature: string; + optional: bool; + deprecated: string option; +} + +type constructorDoc = { + constructorName: string; + docstrings: string list; + signature: string; + deprecated: string option; +} + +type docItemDetail = + | Record of {fieldDocs: fieldDoc list} + | Variant of {constructorDocs: constructorDoc list} +type docItem = + | Value of { + id: string; + docstring: string list; + signature: string; + name: string; + deprecated: string option; + } + | Type of { + id: string; + docstring: string list; + signature: string; + name: string; + deprecated: string option; + detail: docItemDetail option; + (** Additional documentation for constructors and record fields, if available. *) + } + | Module of docsForModule + | ModuleAlias of { + id: string; + docstring: string list; + name: string; + items: docItem list; + } +and docsForModule = { + id: string; + docstring: string list; + deprecated: string option; + name: string; + items: docItem list; +} + +let formatCode content = + let {Res_driver.parsetree = signature; comments} = + Res_driver.parseInterfaceFromSource ~forPrinter:true + ~displayFilename:"" ~source:content + in + Res_printer.printInterface ~width:!Res_cli.ResClflags.width ~comments + signature + |> String.trim + +let stringifyDocstrings docstrings = + let open Protocol in + docstrings + |> List.map (fun docstring -> docstring |> String.trim |> wrapInQuotes) + |> array + +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 (fun fieldDoc -> + 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)); + ]) + |> 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) ); + ]) + |> array) ); + ] + +let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) = + let open Protocol in + match item with + | Value {id; docstring; signature; name; deprecated} -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes id)); + ("kind", Some (wrapInQuotes "value")); + ("name", Some (name |> Json.escape |> wrapInQuotes)); + ( "deprecated", + match deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ( "signature", + Some (signature |> String.trim |> Json.escape |> wrapInQuotes) ); + ("docstrings", Some (stringifyDocstrings docstring)); + ] + | Type {id; docstring; signature; name; deprecated; detail} -> + stringifyObject ~startOnNewline:true ~indentation + [ + ("id", Some (wrapInQuotes id)); + ("kind", Some (wrapInQuotes "type")); + ("name", Some (name |> Json.escape |> wrapInQuotes)); + ( "deprecated", + match deprecated with + | Some d -> Some (wrapInQuotes d) + | None -> None ); + ("signature", Some (signature |> Json.escape |> wrapInQuotes)); + ("docstrings", Some (stringifyDocstrings docstring)); + ( "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")); + ( "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)); + ( "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)); + ( "items", + Some + (d.items + |> List.map + (stringifyDocItem ~originalEnv ~indentation:(indentation + 1)) + |> array) ); + ] + +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 (fun (field : field) -> + { + fieldName = field.fname.txt; + docstrings = field.docstring; + optional = field.optional; + signature = Shared.typeToString field.typ; + deprecated = field.deprecated; + }); + }) + | 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; + }); + }) + | _ -> None + +let makeId modulePath ~identifier = + identifier :: modulePath |> List.rev |> SharedTypes.ident + +let extractDocs ~path ~debug = + if debug then Printf.printf "extracting docs for %s\n" path; + if + FindFiles.isImplementation path = false + && FindFiles.isInterface path = false + then ( + Printf.eprintf "error: failed to read %s, expected an .res or .resi file\n" + path; + exit 1); + 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 -> + Printf.eprintf + "error: failed to generate doc for %s, try to build the project\n" path; + exit 1 + | Some full -> + let file = full.file in + let structure = file.structure in + let open SharedTypes in + let env = QueryEnv.fromFile file in + let rec extractDocsForModule ?(modulePath = [env.file.moduleName]) + (structure : Module.structure) = + { + id = modulePath |> List.rev |> ident; + docstring = structure.docstring |> List.map String.trim; + name = structure.name; + deprecated = structure.deprecated; + items = + structure.items + |> List.filter_map (fun (item : Module.item) -> + 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 + |> formatCode; + name = item.name; + deprecated = item.deprecated; + }) + | Type (typ, _) -> + Some + (Type + { + id = modulePath |> makeId ~identifier:item.name; + docstring = item.docstring |> List.map String.trim; + signature = + typ.decl + |> Shared.declToString item.name + |> formatCode; + name = item.name; + deprecated = item.deprecated; + detail = typeDetail typ ~full ~env; + }) + | Module (Ident p) -> + (* module Whatever = OtherModule *) + let aliasToModule = p |> pathIdentToString in + let id = + (modulePath |> List.rev |> List.hd) ^ "." ^ item.name + in + let items = + match + ProcessCmt.fileForModule ~package:full.package + aliasToModule + with + | None -> [] + | Some file -> + let docs = + extractDocsForModule ~modulePath:[id] file.structure + in + docs.items + in + Some + (ModuleAlias + { + id; + name = item.name; + items; + docstring = item.docstring |> List.map String.trim; + }) + | Module (Structure m) -> + (* module Whatever = {} in res or module Whatever: {} in resi. *) + Some + (Module + (extractDocsForModule ~modulePath:(m.name :: modulePath) + m)) + | Module (Constraint (Structure _impl, Structure interface)) -> + (* module Whatever: { } = { }. Prefer the interface. *) + Some + (Module + (extractDocsForModule + ~modulePath:(interface.name :: modulePath) + interface)) + | _ -> None); + } + in + let docs = extractDocsForModule structure in + print_endline (stringifyDocsForModule ~originalEnv:env docs) diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml index c610d1e79..60ba88c21 100644 --- a/analysis/src/ProcessAttributes.ml +++ b/analysis/src/ProcessAttributes.ml @@ -5,7 +5,7 @@ let rec findDocAttribute attributes = let open Parsetree in match attributes with | [] -> None - | ( {Asttypes.txt = "ocaml.doc" | "ns.doc" | "res.doc"}, + | ( {Asttypes.txt = "ocaml.doc" | "ocaml.text" | "ns.doc" | "res.doc"}, PStr [ { diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml index c7ecd8525..87fdba2b4 100644 --- a/analysis/src/ProcessCmt.ml +++ b/analysis/src/ProcessCmt.ml @@ -54,7 +54,14 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) newDeclared | _ -> declared in - [{Module.kind = Module.Value declared.item; name = declared.name.txt}] + [ + { + Module.kind = Module.Value declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Sig_type ( ident, ({type_loc; type_kind; type_manifest; type_attributes} as decl), @@ -121,7 +128,14 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) (Exported.add exported Exported.Type) Stamps.addType in - [{Module.kind = Type (declared.item, recStatus); name = declared.name.txt}] + [ + { + Module.kind = Type (declared.item, recStatus); + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Sig_module (ident, {md_type; md_attributes; md_loc}, _) -> let name = Ident.name ident in let declared = @@ -132,7 +146,14 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) (Exported.add exported Exported.Module) Stamps.addModule in - [{Module.kind = Module declared.item; name = declared.name.txt}] + [ + { + Module.kind = Module declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | _ -> [] and forTypeSignature ~name ~env signature = @@ -142,7 +163,7 @@ and forTypeSignature ~name ~env signature = (fun item items -> forTypeSignatureItem ~env ~exported item @ items) signature [] in - {Module.name; docstring = []; exported; items} + {Module.name; docstring = []; exported; items; deprecated = None} and forTypeModule ~name ~env moduleType = match moduleType with @@ -293,6 +314,8 @@ let forTypeDeclaration ~env ~(exported : Exported.t) { Module.kind = Module.Type (declared.item, recStatus); name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; } let rec forSignatureItem ~env ~(exported : Exported.t) @@ -306,7 +329,14 @@ let rec forSignatureItem ~env ~(exported : Exported.t) (Exported.add exported Exported.Value) Stamps.addValue in - [{Module.kind = Module.Value declared.item; name = declared.name.txt}] + [ + { + Module.kind = Module.Value declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Tsig_type (recFlag, decls) -> decls |> List.mapi (fun i decl -> @@ -330,7 +360,14 @@ let rec forSignatureItem ~env ~(exported : Exported.t) (Exported.add exported Exported.Module) Stamps.addModule in - [{Module.kind = Module declared.item; name = declared.name.txt}] + [ + { + Module.kind = Module declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Tsig_recmodule modDecls -> modDecls |> List.map (fun modDecl -> @@ -364,7 +401,8 @@ let forSignature ~name ~env sigItems = | _ -> [] in let docstring = attrsToDocstring attributes in - {Module.name; docstring; exported; items} + let deprecated = ProcessAttributes.findDeprecatedAttribute attributes in + {Module.name; docstring; exported; items; deprecated} let forTreeModuleType ~name ~env {Typedtree.mty_desc} = match mty_desc with @@ -400,7 +438,12 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = Stamps.addValue in items := - {Module.kind = Module.Value declared.item; name = declared.name.txt} + { + Module.kind = Module.Value declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + } :: !items | Tpat_tuple pats | Tpat_array pats | Tpat_construct (_, _, pats) -> pats |> List.iter (fun p -> handlePattern [] p) @@ -429,7 +472,14 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (Exported.add exported Exported.Module) Stamps.addModule in - [{Module.kind = Module declared.item; name = declared.name.txt}] + [ + { + Module.kind = Module declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Tstr_recmodule modDecls -> modDecls |> List.map (fun modDecl -> @@ -453,7 +503,14 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (Exported.add exported Exported.Module) Stamps.addModule in - [{Module.kind = Module modTypeItem; name = declared.name.txt}] + [ + { + Module.kind = Module modTypeItem; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Tstr_include {incl_mod; incl_type} -> let env = match getModulePath incl_mod.mod_desc with @@ -475,7 +532,14 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (Exported.add exported Exported.Value) Stamps.addValue in - [{Module.kind = Value declared.item; name = declared.name.txt}] + [ + { + Module.kind = Value declared.item; + name = declared.name.txt; + docstring = declared.docstring; + deprecated = declared.deprecated; + }; + ] | Tstr_type (recFlag, decls) -> decls |> List.mapi (fun i decl -> @@ -529,12 +593,15 @@ and forStructure ~name ~env strItems = strItems [] in let attributes = - match strItems with - | {str_desc = Tstr_attribute attribute} :: _ -> [attribute] - | _ -> [] + strItems + |> List.filter_map (fun (struc : Typedtree.structure_item) -> + match struc with + | {str_desc = Tstr_attribute attr} -> Some attr + | _ -> None) in let docstring = attrsToDocstring attributes in - {Module.name; docstring; exported; items} + let deprecated = ProcessAttributes.findDeprecatedAttribute attributes in + {Module.name; docstring; exported; items; deprecated} let fileForCmtInfos ~moduleName ~uri ({cmt_modname; cmt_annots} : Cmt_format.cmt_infos) = diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index 1f23f522e..a98007d45 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -98,16 +98,19 @@ let stringifyMarkupContent (m : markupContent) = Printf.sprintf {|{"kind": "%s", "value": "%s"}|} m.kind (Json.escape m.value) (** None values are not emitted in the output. *) -let stringifyObject properties = - {|{ +let stringifyObject ?(startOnNewline = false) ?(indentation = 1) properties = + let indentationStr = String.make (indentation * 2) ' ' in + (if startOnNewline then "\n" ^ indentationStr else "") + ^ {|{ |} ^ (properties |> List.filter_map (fun (key, value) -> match value with | None -> None - | Some v -> Some (Printf.sprintf {| "%s": %s|} key v)) + | Some v -> + Some (Printf.sprintf {|%s "%s": %s|} indentationStr key v)) |> String.concat ",\n") - ^ "\n }" + ^ "\n" ^ indentationStr ^ "}" let wrapInQuotes s = "\"" ^ Json.escape s ^ "\"" diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 7ee60c8e8..e975c6264 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -122,13 +122,19 @@ module Module = struct | Type of Type.t * Types.rec_status | Module of t - and item = {kind: kind; name: string} + and item = { + kind: kind; + name: string; + docstring: string list; + deprecated: string option; + } and structure = { name: string; docstring: string list; exported: Exported.t; items: item list; + deprecated: string option } and t = Ident of Path.t | Structure of structure | Constraint of t * t @@ -253,6 +259,7 @@ module File = struct docstring = []; exported = Exported.init (); items = []; + deprecated = None }; } end diff --git a/analysis/tests/src/DocExtraction2.res b/analysis/tests/src/DocExtraction2.res new file mode 100644 index 000000000..3733153e0 --- /dev/null +++ b/analysis/tests/src/DocExtraction2.res @@ -0,0 +1,12 @@ +type t = string + +let getStr = () => "123" + +let make = getStr + +module InnerModule = { + type t = unit + let make = () => () +} + +// ^dex diff --git a/analysis/tests/src/DocExtraction2.resi b/analysis/tests/src/DocExtraction2.resi new file mode 100644 index 000000000..b653bdcab --- /dev/null +++ b/analysis/tests/src/DocExtraction2.resi @@ -0,0 +1,19 @@ +/*** Module level doc here.*/ + +/** Type t is pretty cool.*/ +type t + +/** Makerz of stuffz. */ +let make: unit => t + +module InnerModule: { + /*** This inner module is nice...*/ + + /** This type is also t. */ + type t + + /** Maker of tea.*/ + let make: unit => t +} + +// ^dex diff --git a/analysis/tests/src/DocExtractionRes.res b/analysis/tests/src/DocExtractionRes.res new file mode 100644 index 000000000..0127e69c1 --- /dev/null +++ b/analysis/tests/src/DocExtractionRes.res @@ -0,0 +1,91 @@ +/***Module level documentation goes here. */ + +/** This type represents stuff. */ +type t = { + /** The name of the stuff.*/ + name: string, + /** Whether stuff is online.*/ + online: bool, +} + +/** Create stuff. + +```rescript example +let stuff = make("My name") +``` +*/ +let make = name => { + name, + online: true, +} + +/** Stuff goes offline.*/ +let asOffline = (t: t) => {...t, online: false} + +module SomeInnerModule = { + /*** Another module level docstring here.*/ + type status = + | /** If this is started or not */ Started(t) | /** Stopped? */ Stopped | /** Now idle.*/ Idle + + /** These are all the valid inputs.*/ + type validInputs = [#something | #"needs-escaping" | #withPayload(int) | #status(status)] + + type callback = (t, ~status: status) => unit +} + +module AnotherModule = { + /*** Mighty fine module here too!*/ + + /** This links another module. Neat. */ + module LinkedModule = SomeInnerModule + + /** + Testing what this looks like.*/ + type callback = SomeInnerModule.status => unit + + let isGoodStatus = (status: SomeInnerModule.status) => status == Stopped + + /** Trying how it looks with an inline record in a variant. */ + type someVariantWithInlineRecords = | /** This has inline records...*/ SomeStuff({offline: bool}) + + open ReactDOM + + /**Callback to get the DOM root...*/ + type domRoot = unit => Client.Root.t +} + +module ModuleWithThingsThatShouldNotBeExported: { + /*** BROKEN: This docstring isn't picked up Doesn't seem to be parsed at all, no attributes found.*/ + + /** The type t is stuff. */ + type t + + /** The maker of stuff!*/ + let make: unit => t +} = { + /*** Mighty fine module here too!*/ + type t = string + type x = int + type f = bool + + let m1 = (x: x) => { + x + 1 + } + + let m2 = (f: f) => + if f { + true + } else { + false + } + + let make = () => { + if m2(true) && m1(1) > 2 { + "1" + } else { + "2" + } + } +} + +// ^dex diff --git a/analysis/tests/src/expected/DocExtraction2.res.txt b/analysis/tests/src/expected/DocExtraction2.res.txt new file mode 100644 index 000000000..7645cfac0 --- /dev/null +++ b/analysis/tests/src/expected/DocExtraction2.res.txt @@ -0,0 +1,44 @@ +Documentation extraction src/DocExtraction2.res +extracting docs for src/DocExtraction2.res +preferring found resi file for impl: src/DocExtraction2.resi + +{ + "name": "DocExtraction2", + "docstrings": ["Module level doc here."], + "items": [ + { + "id": "DocExtraction2.t", + "kind": "type", + "name": "t", + "signature": "type t", + "docstrings": ["Type t is pretty cool."] + }, + { + "id": "DocExtraction2.make", + "kind": "value", + "name": "make", + "signature": "let make: unit => t", + "docstrings": ["Makerz of stuffz."] + }, + { + "id": "DocExtraction2.InnerModule", + "name": "InnerModule", + "kind": "module", + "items": [ + { + "id": "DocExtraction2.InnerModule.t", + "kind": "type", + "name": "t", + "signature": "type t", + "docstrings": ["This type is also t."] + }, + { + "id": "DocExtraction2.InnerModule.make", + "kind": "value", + "name": "make", + "signature": "let make: unit => t", + "docstrings": ["Maker of tea."] + }] + }] +} + diff --git a/analysis/tests/src/expected/DocExtraction2.resi.txt b/analysis/tests/src/expected/DocExtraction2.resi.txt new file mode 100644 index 000000000..51d938f0c --- /dev/null +++ b/analysis/tests/src/expected/DocExtraction2.resi.txt @@ -0,0 +1,43 @@ +Documentation extraction src/DocExtraction2.resi +extracting docs for src/DocExtraction2.resi + +{ + "name": "DocExtraction2", + "docstrings": ["Module level doc here."], + "items": [ + { + "id": "DocExtraction2.t", + "kind": "type", + "name": "t", + "signature": "type t", + "docstrings": ["Type t is pretty cool."] + }, + { + "id": "DocExtraction2.make", + "kind": "value", + "name": "make", + "signature": "let make: unit => t", + "docstrings": ["Makerz of stuffz."] + }, + { + "id": "DocExtraction2.InnerModule", + "name": "InnerModule", + "kind": "module", + "items": [ + { + "id": "DocExtraction2.InnerModule.t", + "kind": "type", + "name": "t", + "signature": "type t", + "docstrings": ["This type is also t."] + }, + { + "id": "DocExtraction2.InnerModule.make", + "kind": "value", + "name": "make", + "signature": "let make: unit => t", + "docstrings": ["Maker of tea."] + }] + }] +} + diff --git a/analysis/tests/src/expected/DocExtractionRes.res.txt b/analysis/tests/src/expected/DocExtractionRes.res.txt new file mode 100644 index 000000000..b591bfd91 --- /dev/null +++ b/analysis/tests/src/expected/DocExtractionRes.res.txt @@ -0,0 +1,163 @@ +Documentation extraction src/DocExtractionRes.res +extracting docs for src/DocExtractionRes.res + +{ + "name": "DocExtractionRes", + "docstrings": ["Module level documentation goes here."], + "items": [ + { + "id": "DocExtractionRes.t", + "kind": "type", + "name": "t", + "signature": "type t = {name: string, online: bool}", + "docstrings": ["This type represents stuff."], + "detail": + { + "kind": "record", + "items": [{ + "name": "name", + "optional": false, + "docstrings": ["The name of the stuff."], + "signature": "string" + }, { + "name": "online", + "optional": false, + "docstrings": ["Whether stuff is online."], + "signature": "bool" + }] + } + }, + { + "id": "DocExtractionRes.make", + "kind": "value", + "name": "make", + "signature": "let make: string => t", + "docstrings": ["Create stuff.\n\n```rescript example\nlet stuff = make(\"My name\")\n```"] + }, + { + "id": "DocExtractionRes.asOffline", + "kind": "value", + "name": "asOffline", + "signature": "let asOffline: t => t", + "docstrings": ["Stuff goes offline."] + }, + { + "id": "DocExtractionRes.SomeInnerModule", + "name": "SomeInnerModule", + "kind": "module", + "items": [ + { + "id": "DocExtractionRes.SomeInnerModule.status", + "kind": "type", + "name": "status", + "signature": "type status = Started(t) | Stopped | Idle", + "docstrings": [], + "detail": + { + "kind": "variant", + "items": [ + { + "name": "Started", + "docstrings": ["If this is started or not"], + "signature": "Started(t)" + }, + { + "name": "Stopped", + "docstrings": ["Stopped?"], + "signature": "Stopped" + }, + { + "name": "Idle", + "docstrings": ["Now idle."], + "signature": "Idle" + }] + } + }, + { + "id": "DocExtractionRes.SomeInnerModule.validInputs", + "kind": "type", + "name": "validInputs", + "signature": "type validInputs = [\\n | #\\\"needs-escaping\\\"\\n | #something\\n | #status(status)\\n | #withPayload(int)\\n]", + "docstrings": ["These are all the valid inputs."] + }, + { + "id": "DocExtractionRes.SomeInnerModule.callback", + "kind": "type", + "name": "callback", + "signature": "type callback = (t, ~status: status) => unit", + "docstrings": [] + }] + }, + { + "id": "DocExtractionRes.AnotherModule", + "name": "AnotherModule", + "kind": "module", + "items": [ + { + "id": "DocExtractionRes.LinkedModule", + "kind": "moduleAlias", + "name": "LinkedModule", + "docstrings": ["This links another module. Neat."], + "items": [] + }, + { + "id": "DocExtractionRes.AnotherModule.callback", + "kind": "type", + "name": "callback", + "signature": "type callback = SomeInnerModule.status => unit", + "docstrings": ["Testing what this looks like."] + }, + { + "id": "DocExtractionRes.AnotherModule.isGoodStatus", + "kind": "value", + "name": "isGoodStatus", + "signature": "let isGoodStatus: SomeInnerModule.status => bool", + "docstrings": [] + }, + { + "id": "DocExtractionRes.AnotherModule.someVariantWithInlineRecords", + "kind": "type", + "name": "someVariantWithInlineRecords", + "signature": "type someVariantWithInlineRecords = SomeStuff({offline: bool})", + "docstrings": ["Trying how it looks with an inline record in a variant."], + "detail": + { + "kind": "variant", + "items": [ + { + "name": "SomeStuff", + "docstrings": ["This has inline records..."], + "signature": "SomeStuff" + }] + } + }, + { + "id": "DocExtractionRes.AnotherModule.domRoot", + "kind": "type", + "name": "domRoot", + "signature": "type domRoot = unit => ReactDOM.Client.Root.t", + "docstrings": ["Callback to get the DOM root..."] + }] + }, + { + "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported", + "name": "ModuleWithThingsThatShouldNotBeExported", + "kind": "module", + "items": [ + { + "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.t", + "kind": "type", + "name": "t", + "signature": "type t", + "docstrings": ["The type t is stuff."] + }, + { + "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.make", + "kind": "value", + "name": "make", + "signature": "let make: unit => t", + "docstrings": ["The maker of stuff!"] + }] + }] +} + diff --git a/client/src/extension.ts b/client/src/extension.ts index 1ef2e2bca..ada9f360c 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -203,6 +203,7 @@ export function activate(context: ExtensionContext) { customCommands.openCompiled(client); }); + commands.registerCommand( "rescript-vscode.go_to_location", async (fileUri: string, startLine: number, startCol: number) => { diff --git a/server/src/server.ts b/server/src/server.ts index 3e36c990a..3bc5b3ec0 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -152,6 +152,7 @@ let openCompiledFileRequest = new v.RequestType< void >("textDocument/openCompiled"); + let getCurrentCompilerDiagnosticsForFile = ( fileUri: string ): p.Diagnostic[] => {