Skip to content

Commit 6e4e49e

Browse files
committed
support inline record fields in doc extraction
1 parent d39e28c commit 6e4e49e

File tree

7 files changed

+77
-211
lines changed

7 files changed

+77
-211
lines changed

analysis/src/CompletionBackEnd.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@ open SharedTypes
33
let showConstructor {Constructor.cname = {txt}; args; res} =
44
txt
55
^ (match args with
6-
| Args [] | InlineRecord _ -> ""
6+
| Args [] -> ""
7+
| InlineRecord fields ->
8+
"({"
9+
^ (fields
10+
|> List.map (fun (field : field) ->
11+
Printf.sprintf "%s%s: %s" field.fname.txt
12+
(if field.optional then "?" else "")
13+
(Shared.typeToString
14+
(if field.optional then Utils.unwrapIfOption field.typ
15+
else field.typ)))
16+
|> String.concat ", ")
17+
^ "})"
718
| Args args ->
819
"("
920
^ (args

analysis/src/DocExtraction.ml

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type constructorDoc = {
1111
docstrings: string list;
1212
signature: string;
1313
deprecated: string option;
14+
inlineRecordFields: fieldDoc list option;
1415
}
1516

1617
type docItemDetail =
@@ -54,6 +55,20 @@ let stringifyDocstrings docstrings =
5455
|> List.map (fun docstring -> docstring |> String.trim |> wrapInQuotes)
5556
|> array
5657

58+
let stringifyFieldDoc ~indentation (fieldDoc : fieldDoc) =
59+
let open Protocol in
60+
stringifyObject ~indentation:(indentation + 1)
61+
[
62+
("name", Some (wrapInQuotes fieldDoc.fieldName));
63+
( "deprecated",
64+
match fieldDoc.deprecated with
65+
| Some d -> Some (wrapInQuotes d)
66+
| None -> None );
67+
("optional", Some (string_of_bool fieldDoc.optional));
68+
("docstrings", Some (stringifyDocstrings fieldDoc.docstrings));
69+
("signature", Some (wrapInQuotes fieldDoc.signature));
70+
]
71+
5772
let stringifyDetail ?(indentation = 0) (detail : docItemDetail) =
5873
let open Protocol in
5974
match detail with
@@ -62,22 +77,8 @@ let stringifyDetail ?(indentation = 0) (detail : docItemDetail) =
6277
[
6378
("kind", Some (wrapInQuotes "record"));
6479
( "items",
65-
Some
66-
(fieldDocs
67-
|> List.map (fun fieldDoc ->
68-
stringifyObject ~indentation:(indentation + 1)
69-
[
70-
("name", Some (wrapInQuotes fieldDoc.fieldName));
71-
( "deprecated",
72-
match fieldDoc.deprecated with
73-
| Some d -> Some (wrapInQuotes d)
74-
| None -> None );
75-
("optional", Some (string_of_bool fieldDoc.optional));
76-
( "docstrings",
77-
Some (stringifyDocstrings fieldDoc.docstrings) );
78-
("signature", Some (wrapInQuotes fieldDoc.signature));
79-
])
80-
|> array) );
80+
Some (fieldDocs |> List.map (stringifyFieldDoc ~indentation) |> array)
81+
);
8182
]
8283
| Variant {constructorDocs} ->
8384
stringifyObject ~startOnNewline:true ~indentation
@@ -100,6 +101,16 @@ let stringifyDetail ?(indentation = 0) (detail : docItemDetail) =
100101
Some (stringifyDocstrings constructorDoc.docstrings) );
101102
( "signature",
102103
Some (wrapInQuotes constructorDoc.signature) );
104+
( "inlineRecordFields",
105+
match constructorDoc.inlineRecordFields with
106+
| None -> None
107+
| Some fieldDocs ->
108+
Some
109+
(fieldDocs
110+
|> List.map
111+
(stringifyFieldDoc
112+
~indentation:(indentation + 1))
113+
|> array) );
103114
])
104115
|> array) );
105116
]
@@ -185,24 +196,20 @@ and stringifyDocsForModule ?(indentation = 0) ~originalEnv (d : docsForModule) =
185196
|> array) );
186197
]
187198

199+
let fieldToFieldDoc (field : SharedTypes.field) : fieldDoc =
200+
{
201+
fieldName = field.fname.txt;
202+
docstrings = field.docstring;
203+
optional = field.optional;
204+
signature = Shared.typeToString field.typ;
205+
deprecated = field.deprecated;
206+
}
207+
188208
let typeDetail typ ~env ~full =
189209
let open SharedTypes in
190210
match TypeUtils.extractTypeFromResolvedType ~env ~full typ with
191211
| Some (Trecord {fields}) ->
192-
Some
193-
(Record
194-
{
195-
fieldDocs =
196-
fields
197-
|> List.map (fun (field : field) ->
198-
{
199-
fieldName = field.fname.txt;
200-
docstrings = field.docstring;
201-
optional = field.optional;
202-
signature = Shared.typeToString field.typ;
203-
deprecated = field.deprecated;
204-
});
205-
})
212+
Some (Record {fieldDocs = fields |> List.map fieldToFieldDoc})
206213
| Some (Tvariant {constructors}) ->
207214
Some
208215
(Variant
@@ -215,6 +222,11 @@ let typeDetail typ ~env ~full =
215222
docstrings = c.docstring;
216223
signature = CompletionBackEnd.showConstructor c;
217224
deprecated = c.deprecated;
225+
inlineRecordFields =
226+
(match c.args with
227+
| InlineRecord fields ->
228+
Some (fields |> List.map fieldToFieldDoc)
229+
| _ -> None);
218230
});
219231
})
220232
| _ -> None

analysis/tests/src/DocExtractionRes.res

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@ module AnotherModule = {
4949
let isGoodStatus = (status: SomeInnerModule.status) => status == Stopped
5050
5151
/** Trying how it looks with an inline record in a variant. */
52-
type someVariantWithInlineRecords = | /** This has inline records...*/ SomeStuff({offline: bool})
52+
type someVariantWithInlineRecords =
53+
| /** This has inline records...*/
54+
SomeStuff({
55+
offline: bool,
56+
/** Is the user online? */ online?: bool,
57+
})
5358
5459
open ReactDOM
5560

analysis/tests/src/expected/DocExtractionRes.res.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ extracting docs for src/DocExtractionRes.res
125125
"id": "DocExtractionRes.AnotherModule.someVariantWithInlineRecords",
126126
"kind": "type",
127127
"name": "someVariantWithInlineRecords",
128-
"signature": "type someVariantWithInlineRecords =\n | SomeStuff({offline: bool})",
128+
"signature": "type someVariantWithInlineRecords =\n | SomeStuff({offline: bool, online?: bool})",
129129
"docstrings": ["Trying how it looks with an inline record in a variant."],
130130
"detail":
131131
{
@@ -134,7 +134,18 @@ extracting docs for src/DocExtractionRes.res
134134
{
135135
"name": "SomeStuff",
136136
"docstrings": ["This has inline records..."],
137-
"signature": "SomeStuff"
137+
"signature": "SomeStuff({offline: bool, online?: bool})",
138+
"inlineRecordFields": [{
139+
"name": "offline",
140+
"optional": false,
141+
"docstrings": [],
142+
"signature": "bool"
143+
}, {
144+
"name": "online",
145+
"optional": true,
146+
"docstrings": ["Is the user online?"],
147+
"signature": "option<bool>"
148+
}]
138149
}]
139150
}
140151
},

tools/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#### :bug: Bug Fix
1616

17+
- Support inline record fields in constructors.
1718
- Fix docstrings for module alias. Get internal docstrings of module file. https://github.com/rescript-lang/rescript-vscode/pull/878
1819
- Fix extracted docs of types include escaped linebreaks in signature. https://github.com/rescript-lang/rescript-vscode/pull/891
1920

tools/src/Tools_Docgen.res

Lines changed: 2 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type constructor = {
1111
docstrings: array<string>,
1212
signature: string,
1313
deprecated?: string,
14+
inlineRecordFields?: array<field>,
1415
}
1516

1617
@tag("kind")
@@ -54,159 +55,6 @@ type rec item =
5455
items: array<item>,
5556
})
5657

57-
let decodeDocstrings = item => {
58-
open Js.Json
59-
switch item->Js.Dict.get("docstrings") {
60-
| Some(Array(arr)) =>
61-
arr->Js.Array2.map(s =>
62-
switch s {
63-
| String(s) => s
64-
| _ => assert(false)
65-
}
66-
)
67-
| _ => []
68-
}
69-
}
70-
71-
let decodeStringByField = (item, field) => {
72-
open Js.Json
73-
switch item->Js.Dict.get(field) {
74-
| Some(String(s)) => s
75-
| _ => assert(false)
76-
}
77-
}
78-
79-
let decodeDepreacted = item => {
80-
open Js.Json
81-
switch item->Js.Dict.get("deprecated") {
82-
| Some(String(s)) => Some(s)
83-
| _ => None
84-
}
85-
}
86-
87-
let decodeRecordFields = fields => {
88-
open Js.Json
89-
let items = fields->Js.Array2.map(field => {
90-
switch field {
91-
| Object(doc) => {
92-
let name = doc->decodeStringByField("name")
93-
let docstrings = doc->decodeDocstrings
94-
let signature = doc->decodeStringByField("signature")
95-
let deprecated = doc->decodeDepreacted
96-
let optional = switch Js.Dict.get(doc, "optional") {
97-
| Some(Boolean(bool)) => bool
98-
| _ => assert(false)
99-
}
100-
101-
{name, docstrings, signature, optional, ?deprecated}
102-
}
103-
104-
| _ => assert(false)
105-
}
106-
})
107-
Record({items: items})
108-
}
109-
110-
let decodeConstructorFields = fields => {
111-
open Js.Json
112-
let items = fields->Js.Array2.map(field => {
113-
switch field {
114-
| Object(doc) => {
115-
let name = doc->decodeStringByField("name")
116-
let docstrings = doc->decodeDocstrings
117-
let signature = doc->decodeStringByField("signature")
118-
let deprecated = doc->decodeDepreacted
119-
120-
{name, docstrings, signature, ?deprecated}
121-
}
122-
123-
| _ => assert(false)
124-
}
125-
})
126-
Variant({items: items})
127-
}
128-
129-
let decodeDetail = detail => {
130-
open Js.Json
131-
132-
switch detail {
133-
| Object(detail) =>
134-
switch (detail->Js.Dict.get("kind"), detail->Js.Dict.get("items")) {
135-
| (Some(String(kind)), Some(Array(items))) =>
136-
switch kind {
137-
| "record" => decodeRecordFields(items)
138-
| "variant" => decodeConstructorFields(items)
139-
| _ => assert(false)
140-
}
141-
| _ => assert(false)
142-
}
143-
144-
| _ => assert(false)
145-
}
146-
}
147-
148-
let rec decodeValue = item => {
149-
let id = item->decodeStringByField("id")
150-
let signature = item->decodeStringByField("signature")
151-
let name = item->decodeStringByField("name")
152-
let deprecated = item->decodeDepreacted
153-
let docstrings = item->decodeDocstrings
154-
Value({id, docstrings, signature, name, ?deprecated})
155-
}
156-
and decodeType = item => {
157-
let id = item->decodeStringByField("id")
158-
let signature = item->decodeStringByField("signature")
159-
let name = item->decodeStringByField("name")
160-
let deprecated = item->decodeDepreacted
161-
let docstrings = item->decodeDocstrings
162-
let detail = switch item->Js_dict.get("detail") {
163-
| Some(field) => decodeDetail(field)->Some
164-
| None => None
165-
}
166-
Type({id, docstrings, signature, name, ?deprecated, ?detail})
167-
}
168-
and decodeModuleAlias = item => {
169-
open Js.Json
170-
let id = item->decodeStringByField("id")
171-
let name = item->decodeStringByField("name")
172-
let docstrings = item->decodeDocstrings
173-
let items = switch Js.Dict.get(item, "items") {
174-
| Some(Array(items)) => items->Js.Array2.map(item => decodeItem(item))
175-
| _ => assert(false)
176-
}
177-
ModuleAlias({id, items, name, docstrings})
178-
}
179-
and decodeModule = item => {
180-
open Js.Json
181-
let id = item->decodeStringByField("id")
182-
let name = item->decodeStringByField("name")
183-
let deprecated = item->decodeDepreacted
184-
let docstrings = item->decodeDocstrings
185-
let items = switch Js.Dict.get(item, "items") {
186-
| Some(Array(items)) => items->Js.Array2.map(item => decodeItem(item))
187-
| _ => assert(false)
188-
}
189-
Module({id, name, docstrings, ?deprecated, items})
190-
}
191-
and decodeItem = item => {
192-
open Js.Json
193-
switch item {
194-
| Object(value) =>
195-
switch Js.Dict.get(value, "kind") {
196-
| Some(String(kind)) =>
197-
switch kind {
198-
| "type" => decodeType(value)
199-
| "value" => decodeValue(value)
200-
| "module" => decodeModule(value)
201-
| "moduleAlias" => decodeModuleAlias(value)
202-
| _ => assert(false)
203-
}
204-
| _ => assert(false)
205-
}
206-
| _ => assert(false)
207-
}
208-
}
209-
21058
type doc = {
21159
name: string,
21260
deprecated: option<string>,
@@ -217,22 +65,4 @@ type doc = {
21765
/**
21866
`decodeFromJson(json)` parse JSON generated from `restool doc` command
21967
*/
220-
let decodeFromJson = json => {
221-
open Js.Json
222-
223-
switch json {
224-
| Object(mod) => {
225-
let name = mod->decodeStringByField("name")
226-
let deprecated = mod->decodeDepreacted
227-
let docstrings = mod->decodeDocstrings
228-
let items = switch Js.Dict.get(mod, "items") {
229-
| Some(Array(items)) => items->Js.Array2.map(item => decodeItem(item))
230-
| _ => assert(false)
231-
}
232-
233-
{name, deprecated, docstrings, items}
234-
}
235-
236-
| _ => assert(false)
237-
}
238-
}
68+
external decodeFromJson: Js.Json.t => doc = "%identity"

tools/src/Tools_Docgen.resi

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type constructor = {
1010
docstrings: array<string>,
1111
signature: string,
1212
deprecated?: string,
13+
inlineRecordFields?: array<field>,
1314
}
1415
@tag("kind")
1516
type detail =
@@ -54,9 +55,4 @@ type rec item =
5455

5556
type doc = {name: string, deprecated: option<string>, docstrings: array<string>, items: array<item>}
5657

57-
let decodeValue: Js.Dict.t<Js.Json.t> => item
58-
let decodeType: Js.Dict.t<Js.Json.t> => item
59-
let decodeModule: Js.Dict.t<Js.Json.t> => item
60-
let decodeModuleAlias: Js.Dict.t<Js.Json.t> => item
61-
let decodeItem: Js.Json.t => item
6258
let decodeFromJson: Js.Json.t => doc

0 commit comments

Comments
 (0)