From 3b1014ea4da9f2ee70b1b60f21ed5297cdf9618a Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Tue, 22 Oct 2024 16:53:03 +0200 Subject: [PATCH] Add additional signature information to doc json (#1043) * Add signature to details * Include Tsubst and Tpoly * Update processing of Tpoly --- tools/bin/main.ml | 2 +- tools/npm/Tools_Docgen.res | 13 +++ tools/npm/Tools_Docgen.resi | 14 +++ tools/src/tools.ml | 99 +++++++++++++++- .../src/expected/DocExtraction2.res.json | 24 ++++ .../src/expected/DocExtraction2.resi.json | 24 ++++ .../src/expected/DocExtractionRes.res.json | 108 ++++++++++++++++++ tools/tests/src/expected/ModC.res.json | 9 ++ tools/tests/src/expected/ModC.resi.json | 9 ++ 9 files changed, 300 insertions(+), 2 deletions(-) diff --git a/tools/bin/main.ml b/tools/bin/main.ml index 06be383b1..bde33dc0f 100644 --- a/tools/bin/main.ml +++ b/tools/bin/main.ml @@ -14,7 +14,7 @@ Usage: rescript-tools [command] Commands: -doc Generate documentation +doc Generate documentation reanalyze Reanalyze -v, --version Print version -h, --help Print help|} diff --git a/tools/npm/Tools_Docgen.res b/tools/npm/Tools_Docgen.res index c6b7d450c..197a5ee05 100644 --- a/tools/npm/Tools_Docgen.res +++ b/tools/npm/Tools_Docgen.res @@ -17,10 +17,21 @@ type constructor = { payload?: constructorPayload, } +type rec typeInSignature = { + path: string, + genericTypeParameters: array, +} + +type signatureDetais = { + parameters: array, + returnType: typeInSignature, +} + @tag("kind") type detail = | @as("record") Record({items: array}) | @as("variant") Variant({items: array}) + | @as("alias") Signature(signatureDetais) type source = { filepath: string, @@ -38,6 +49,8 @@ type rec item = name: string, deprecated?: string, source: source, + /** Additional documentation of signature, if available. */ + detail?: detail, }) | @as("type") Type({ diff --git a/tools/npm/Tools_Docgen.resi b/tools/npm/Tools_Docgen.resi index 271f65f99..3328ba671 100644 --- a/tools/npm/Tools_Docgen.resi +++ b/tools/npm/Tools_Docgen.resi @@ -16,10 +16,22 @@ type constructor = { deprecated?: string, payload?: constructorPayload, } + +type rec typeInSignature = { + path: string, + genericTypeParameters: array, +} + +type signatureDetais = { + parameters: array, + returnType: typeInSignature, +} + @tag("kind") type detail = | @as("record") Record({items: array}) | @as("variant") Variant({items: array}) + | @as("signature") Signature(signatureDetais) type source = { filepath: string, @@ -37,6 +49,8 @@ type rec item = name: string, deprecated?: string, source: source, + /** Additional documentation of signature, if available. */ + detail?: detail, }) | @as("type") Type({ diff --git a/tools/src/tools.ml b/tools/src/tools.ml index 08837943b..3af0bc390 100644 --- a/tools/src/tools.ml +++ b/tools/src/tools.ml @@ -18,11 +18,15 @@ type constructorDoc = { 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 { @@ -31,6 +35,7 @@ type docItem = signature: string; name: string; deprecated: string option; + detail: docItemDetail option; source: source; } | Type of { @@ -104,6 +109,19 @@ let stringifyConstructorPayload ~indentation |> 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 @@ -147,6 +165,25 @@ let stringifyDetail ?(indentation = 0) (detail : docItemDetail) = ]) |> 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")); + ( "items", + Some + (stringifyObject ~startOnNewline:false ~indentation + [ + ("parameters", ps); + ("returnType", Some (stringifyTypeDoc ~indentation returnType)); + ]) ); + ] let stringifySource ~indentation source = let open Protocol in @@ -160,7 +197,7 @@ let stringifySource ~indentation source = let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) = let open Protocol in match item with - | Value {id; docstring; signature; name; deprecated; source} -> + | Value {id; docstring; signature; name; deprecated; source; detail} -> stringifyObject ~startOnNewline:true ~indentation [ ("id", Some (wrapInQuotes id)); @@ -173,6 +210,11 @@ let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) = ("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 @@ -310,6 +352,60 @@ let typeDetail typ ~env ~full = }) | _ -> 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_desc : Types.type_desc) = + match typ_desc with + | Tlink t | Tsubst t | Tpoly (t, []) -> collectSignatureTypes t.desc + | Tconstr (Path.Pident {name = "function$"}, [t; _], _) -> + collectSignatureTypes t.desc + | 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.desc) + in + [{path = p; genericParameters = ts}]) + | Tarrow (_, t1, t2, _) -> + collectSignatureTypes t1.desc @ collectSignatureTypes t2.desc + | Tvar None -> [{path = "_"; genericParameters = []}] + | _ -> [] + in + match collectSignatureTypes typ.desc with + | [] -> None + | ts -> + let parameters, returnType = splitLast ts in + Some (Signature {parameters; returnType}) + let makeId modulePath ~identifier = identifier :: modulePath |> List.rev |> SharedTypes.ident @@ -398,6 +494,7 @@ let extractDocs ~entryPointFile ~debug = ^ Shared.typeToString typ; name = item.name; deprecated = item.deprecated; + detail = valueDetail typ; source; }) | Type (typ, _) -> diff --git a/tools/tests/src/expected/DocExtraction2.res.json b/tools/tests/src/expected/DocExtraction2.res.json index 224daefdc..fd0e7784e 100644 --- a/tools/tests/src/expected/DocExtraction2.res.json +++ b/tools/tests/src/expected/DocExtraction2.res.json @@ -30,6 +30,18 @@ "filepath": "src/DocExtraction2.resi", "line": 7, "col": 1 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "unit" + }], + "returnType": { + "path": "t" + } + } } }, { @@ -65,6 +77,18 @@ "filepath": "src/DocExtraction2.resi", "line": 15, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "unit" + }], + "returnType": { + "path": "t" + } + } } }] }] diff --git a/tools/tests/src/expected/DocExtraction2.resi.json b/tools/tests/src/expected/DocExtraction2.resi.json index 224daefdc..fd0e7784e 100644 --- a/tools/tests/src/expected/DocExtraction2.resi.json +++ b/tools/tests/src/expected/DocExtraction2.resi.json @@ -30,6 +30,18 @@ "filepath": "src/DocExtraction2.resi", "line": 7, "col": 1 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "unit" + }], + "returnType": { + "path": "t" + } + } } }, { @@ -65,6 +77,18 @@ "filepath": "src/DocExtraction2.resi", "line": 15, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "unit" + }], + "returnType": { + "path": "t" + } + } } }] }] diff --git a/tools/tests/src/expected/DocExtractionRes.res.json b/tools/tests/src/expected/DocExtractionRes.res.json index 93a727b3b..bf7fa2034 100644 --- a/tools/tests/src/expected/DocExtractionRes.res.json +++ b/tools/tests/src/expected/DocExtractionRes.res.json @@ -45,6 +45,18 @@ "filepath": "src/DocExtractionRes.res", "line": 17, "col": 5 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "string" + }], + "returnType": { + "path": "t" + } + } } }, { @@ -57,6 +69,18 @@ "filepath": "src/DocExtractionRes.res", "line": 23, "col": 5 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "t" + }], + "returnType": { + "path": "t" + } + } } }, { @@ -69,6 +93,15 @@ "filepath": "src/DocExtractionRes.res", "line": 26, "col": 5 + }, + "detail": + { + "kind": "signature", + "items": { + "returnType": { + "path": "int" + } + } } }, { @@ -184,6 +217,18 @@ "filepath": "src/DocExtractionRes.res", "line": 49, "col": 7 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "SomeInnerModule.status" + }], + "returnType": { + "path": "bool" + } + } } }, { @@ -268,6 +313,18 @@ "filepath": "src/DocExtractionRes.res", "line": 71, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "unit" + }], + "returnType": { + "path": "t" + } + } } }] }, @@ -304,6 +361,18 @@ "filepath": "src/DocExtractionRes.res", "line": 109, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "t" + }], + "returnType": { + "path": "t" + } + } } }] }, @@ -341,6 +410,18 @@ "filepath": "src/DocExtractionRes.res", "line": 128, "col": 7 + }, + "detail": + { + "kind": "signature", + "items": { + "parameters": [{ + "path": "int" + }], + "returnType": { + "path": "int" + } + } } }] }, @@ -365,6 +446,15 @@ "filepath": "src/DocExtractionRes.res", "line": 132, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "returnType": { + "path": "int" + } + } } }] }, @@ -390,6 +480,15 @@ "filepath": "src/DocExtractionRes.res", "line": 136, "col": 7 + }, + "detail": + { + "kind": "signature", + "items": { + "returnType": { + "path": "int" + } + } } }] }, @@ -426,6 +525,15 @@ "filepath": "src/DocExtractionRes.res", "line": 141, "col": 9 + }, + "detail": + { + "kind": "signature", + "items": { + "returnType": { + "path": "int" + } + } } }] }] diff --git a/tools/tests/src/expected/ModC.res.json b/tools/tests/src/expected/ModC.res.json index 4f68f6191..031d09bf8 100644 --- a/tools/tests/src/expected/ModC.res.json +++ b/tools/tests/src/expected/ModC.res.json @@ -29,6 +29,15 @@ "filepath": "src/ModC.resi", "line": 5, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "returnType": { + "path": "string" + } + } } }] }] diff --git a/tools/tests/src/expected/ModC.resi.json b/tools/tests/src/expected/ModC.resi.json index 4f68f6191..031d09bf8 100644 --- a/tools/tests/src/expected/ModC.resi.json +++ b/tools/tests/src/expected/ModC.resi.json @@ -29,6 +29,15 @@ "filepath": "src/ModC.resi", "line": 5, "col": 3 + }, + "detail": + { + "kind": "signature", + "items": { + "returnType": { + "path": "string" + } + } } }] }]