Skip to content

Commit 9036955

Browse files
zthGabriel Nordeborn
authored and
Gabriel Nordeborn
committed
basic extraction of linkables
1 parent 1b6ce4d commit 9036955

File tree

3 files changed

+210
-23
lines changed

3 files changed

+210
-23
lines changed

analysis/src/DocExtraction.ml

Lines changed: 124 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,125 @@
1-
let formatCode content =
2-
let {Res_driver.parsetree = signature; comments} =
3-
Res_driver.parseInterfaceFromSource ~forPrinter:true
4-
~displayFilename:"<missing-file>" ~source:content
5-
in
6-
Res_printer.printInterface ~width:!Res_cli.ResClflags.width ~comments
7-
signature
8-
|> String.trim
1+
type linkableType = {
2+
name: string;
3+
path: Path.t;
4+
env: SharedTypes.QueryEnv.t;
5+
loc: Location.t;
6+
}
97

108
type docItemDetail =
119
| Record of {fieldDocs: (string * string list) list}
1210
| Variant of {constructorDocs: (string * string list) list}
1311
type docItem =
14-
| Value of {docstring: string list; signature: string; name: string}
12+
| Value of {
13+
docstring: string list;
14+
signature: string;
15+
name: string;
16+
linkables: linkableType list;
17+
(** Relevant types to link to, found in relation to this value. *)
18+
}
1519
| Type of {
1620
docstring: string list;
1721
signature: string;
1822
name: string;
1923
detail: docItemDetail option;
24+
(** Additional documentation for constructors and record fields, if available. *)
25+
linkables: linkableType list;
26+
(** Relevant types to link to, found in relation to this type. *)
2027
}
2128
| Module of docsForModule
2229
and docsForModule = {docstring: string list; name: string; items: docItem list}
2330

31+
let formatCode content =
32+
let {Res_driver.parsetree = signature; comments} =
33+
Res_driver.parseInterfaceFromSource ~forPrinter:true
34+
~displayFilename:"<missing-file>" ~source:content
35+
in
36+
Res_printer.printInterface ~width:!Res_cli.ResClflags.width ~comments
37+
signature
38+
|> String.trim
39+
40+
module Linkables = struct
41+
(* TODO: Extend this by going into function arguments, tuples etc... *)
42+
let labelDeclarationsTypes lds =
43+
lds |> List.map (fun (ld : Types.label_declaration) -> ld.ld_type)
44+
45+
let rec linkablesFromDecl (decl : Types.type_declaration) ~env ~full =
46+
match decl.type_kind with
47+
| Type_record (lds, _) -> (env, lds |> labelDeclarationsTypes)
48+
| Type_variant cds ->
49+
( env,
50+
cds
51+
|> List.map (fun (cd : Types.constructor_declaration) ->
52+
let fromArgs =
53+
match cd.cd_args with
54+
| Cstr_tuple ts -> ts
55+
| Cstr_record lds -> lds |> labelDeclarationsTypes
56+
in
57+
match cd.cd_res with
58+
| None -> fromArgs
59+
| Some t -> t :: fromArgs)
60+
|> List.flatten )
61+
| _ -> (
62+
match decl.type_manifest with
63+
| None -> (env, [])
64+
| Some typ -> linkablesFromTyp typ ~env ~full)
65+
66+
and linkablesFromTyp ~env ~(full : SharedTypes.full) typ =
67+
match typ |> Shared.digConstructor with
68+
| Some path -> (
69+
match References.digConstructor ~env ~package:full.package path with
70+
| None -> (env, [typ])
71+
| Some (env1, {item = {decl}}) -> linkablesFromDecl decl ~env:env1 ~full)
72+
| None -> (env, [typ])
73+
74+
type linkableSource =
75+
| Typ of SharedTypes.Type.t
76+
| TypeExpr of Types.type_expr
77+
78+
let findLinkables ~env ~(full : SharedTypes.full) (typ : linkableSource) =
79+
(* Expand definitions of types mentioned in typ.
80+
If typ itself is a record or variant, search its body *)
81+
let envToSearch, typesToSearch =
82+
match typ with
83+
| Typ t -> linkablesFromDecl ~env t.decl ~full
84+
| TypeExpr t -> linkablesFromTyp t ~env ~full
85+
in
86+
let fromConstructorPath ~env path =
87+
match References.digConstructor ~env ~package:full.package path with
88+
| None -> None
89+
| Some (env, {name = {txt}; extentLoc}) ->
90+
if Utils.isUncurriedInternal path then None
91+
else Some {name = txt; env; loc = extentLoc; path}
92+
in
93+
let constructors = Shared.findTypeConstructors typesToSearch in
94+
constructors |> List.filter_map (fromConstructorPath ~env:envToSearch)
95+
end
96+
2497
let stringifyDocstrings docstrings =
2598
let open Protocol in
2699
docstrings
27100
|> List.map (fun docstring ->
28101
docstring |> String.trim |> Json.escape |> wrapInQuotes)
29102
|> array
30103

104+
let stringifyLinkables ?(indentation = 0)
105+
~(originalEnv : SharedTypes.QueryEnv.t) (linkables : linkableType list) =
106+
let open Protocol in
107+
linkables
108+
|> List.map (fun l ->
109+
stringifyObject ~indentation:(indentation + 1)
110+
[
111+
( "path",
112+
Some
113+
(l.path |> SharedTypes.pathIdentToString |> Json.escape
114+
|> wrapInQuotes) );
115+
("moduleName", Some (l.env.file.moduleName |> wrapInQuotes));
116+
( "external",
117+
Some
118+
(Printf.sprintf "%b" (originalEnv.file.uri <> l.env.file.uri))
119+
);
120+
])
121+
|> array
122+
31123
let stringifyDetail ?(indentation = 0) (detail : docItemDetail) =
32124
let open Protocol in
33125
match detail with
@@ -63,25 +155,33 @@ let stringifyDetail ?(indentation = 0) (detail : docItemDetail) =
63155
|> array) );
64156
]
65157

66-
let rec stringifyDocItem ?(indentation = 0) (item : docItem) =
158+
let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) =
67159
let open Protocol in
68160
match item with
69-
| Value {docstring; signature; name} ->
161+
| Value {docstring; signature; name; linkables} ->
70162
stringifyObject ~startOnNewline:true ~indentation
71163
[
72164
("kind", Some (wrapInQuotes "value"));
73165
("name", Some (name |> Json.escape |> wrapInQuotes));
74166
( "signature",
75167
Some (signature |> String.trim |> Json.escape |> wrapInQuotes) );
76168
("docstrings", Some (stringifyDocstrings docstring));
169+
( "linkables",
170+
Some
171+
(stringifyLinkables ~originalEnv ~indentation:(indentation + 1)
172+
linkables) );
77173
]
78-
| Type {docstring; signature; name; detail} ->
174+
| Type {docstring; signature; name; detail; linkables} ->
79175
stringifyObject ~startOnNewline:true ~indentation
80176
[
81177
("kind", Some (wrapInQuotes "type"));
82178
("name", Some (name |> Json.escape |> wrapInQuotes));
83179
("signature", Some (signature |> Json.escape |> wrapInQuotes));
84180
("docstrings", Some (stringifyDocstrings docstring));
181+
( "linkables",
182+
Some
183+
(stringifyLinkables ~originalEnv ~indentation:(indentation + 1)
184+
linkables) );
85185
( "detail",
86186
match detail with
87187
| None -> None
@@ -92,10 +192,13 @@ let rec stringifyDocItem ?(indentation = 0) (item : docItem) =
92192
stringifyObject ~startOnNewline:true ~indentation
93193
[
94194
("kind", Some (wrapInQuotes "module"));
95-
("item", Some (stringifyDocsForModule ~indentation:(indentation + 1) m));
195+
( "item",
196+
Some
197+
(stringifyDocsForModule ~originalEnv ~indentation:(indentation + 1)
198+
m) );
96199
]
97200

98-
and stringifyDocsForModule ?(indentation = 0) (d : docsForModule) =
201+
and stringifyDocsForModule ?(indentation = 0) ~originalEnv (d : docsForModule) =
99202
let open Protocol in
100203
stringifyObject ~startOnNewline:true ~indentation
101204
[
@@ -104,7 +207,8 @@ and stringifyDocsForModule ?(indentation = 0) (d : docsForModule) =
104207
( "items",
105208
Some
106209
(d.items
107-
|> List.map (stringifyDocItem ~indentation:(indentation + 1))
210+
|> List.map
211+
(stringifyDocItem ~originalEnv ~indentation:(indentation + 1))
108212
|> array) );
109213
]
110214

@@ -133,11 +237,15 @@ let extractDocs ~path ~debug =
133237
"let " ^ item.name ^ ": " ^ Shared.typeToString typ
134238
|> formatCode;
135239
name = item.name;
240+
linkables =
241+
TypeExpr typ |> Linkables.findLinkables ~env ~full;
136242
})
137243
| Type (typ, _) ->
138244
Some
139245
(Type
140246
{
247+
linkables =
248+
Typ typ |> Linkables.findLinkables ~env ~full;
141249
docstring = item.docstring |> List.map String.trim;
142250
signature =
143251
typ.decl
@@ -176,4 +284,4 @@ let extractDocs ~path ~debug =
176284
}
177285
in
178286
let docs = extractDocs structure in
179-
print_endline (stringifyDocsForModule docs)
287+
print_endline (stringifyDocsForModule ~originalEnv:env docs)

analysis/tests/src/DocExtractionRes.res

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module SomeInnerModule = {
2828
| /** If this is started or not */ Started(t) | /** Stopped? */ Stopped | /** Now idle.*/ Idle
2929

3030
/** These are all the valid inputs.*/
31-
type validInputs = [#something | #"needs-escaping" | #withPayload(int)]
31+
type validInputs = [#something | #"needs-escaping" | #withPayload(int) | #status(status)]
3232

3333
type callback = (t, ~status: status) => unit
3434
}
@@ -39,6 +39,16 @@ module AnotherModule = {
3939
/**
4040
Testing what this looks like.*/
4141
type callback = SomeInnerModule.status => unit
42+
43+
let isGoodStatus = (status: SomeInnerModule.status) => status == Stopped
44+
45+
/** Trying how it looks with an inline record in a variant. */
46+
type someVariantWithInlineRecords = | /** This has inline records...*/ SomeStuff({offline: bool})
47+
48+
open ReactDOM
49+
50+
/**Callback to get the DOM root...*/
51+
type domRoot = unit => Client.Root.t
4252
}
4353

4454
// ^dex

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

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ extracting docs for src/DocExtractionRes.res
1010
"name": "t",
1111
"signature": "type t = {name: string, online: bool}",
1212
"docstrings": ["This type represents stuff."],
13+
"linkables": [],
1314
"detail":
1415
{
1516
"kind": "record",
@@ -26,13 +27,23 @@ extracting docs for src/DocExtractionRes.res
2627
"kind": "value",
2728
"name": "make",
2829
"signature": "let make: string => t",
29-
"docstrings": ["Create stuff.\\n\\n```rescript example\\nlet stuff = make(\\\"My name\\\")\\n```"]
30+
"docstrings": ["Create stuff.\\n\\n```rescript example\\nlet stuff = make(\\\"My name\\\")\\n```"],
31+
"linkables": [{
32+
"path": "t",
33+
"moduleName": "DocExtractionRes",
34+
"external": false
35+
}]
3036
},
3137
{
3238
"kind": "value",
3339
"name": "asOffline",
3440
"signature": "let asOffline: t => t",
35-
"docstrings": ["Stuff goes offline."]
41+
"docstrings": ["Stuff goes offline."],
42+
"linkables": [{
43+
"path": "t",
44+
"moduleName": "DocExtractionRes",
45+
"external": false
46+
}]
3647
},
3748
{
3849
"kind": "module",
@@ -46,6 +57,11 @@ extracting docs for src/DocExtractionRes.res
4657
"name": "status",
4758
"signature": "type status = Started(t) | Stopped | Idle",
4859
"docstrings": [],
60+
"linkables": [{
61+
"path": "t",
62+
"moduleName": "DocExtractionRes",
63+
"external": false
64+
}],
4965
"detail":
5066
{
5167
"kind": "variant",
@@ -67,14 +83,24 @@ extracting docs for src/DocExtractionRes.res
6783
{
6884
"kind": "type",
6985
"name": "validInputs",
70-
"signature": "type validInputs = [\\n | #\\\"needs-escaping\\\"\\n | #something\\n | #withPayload(int)\\n]",
71-
"docstrings": ["These are all the valid inputs."]
86+
"signature": "type validInputs = [\\n | #\\\"needs-escaping\\\"\\n | #something\\n | #status(status)\\n | #withPayload(int)\\n]",
87+
"docstrings": ["These are all the valid inputs."],
88+
"linkables": []
7289
},
7390
{
7491
"kind": "type",
7592
"name": "callback",
7693
"signature": "type callback = (t, ~status: status) => unit",
77-
"docstrings": []
94+
"docstrings": [],
95+
"linkables": [{
96+
"path": "t",
97+
"moduleName": "DocExtractionRes",
98+
"external": false
99+
}, {
100+
"path": "status",
101+
"moduleName": "DocExtractionRes",
102+
"external": false
103+
}]
78104
}]
79105
}
80106
},
@@ -89,7 +115,50 @@ extracting docs for src/DocExtractionRes.res
89115
"kind": "type",
90116
"name": "callback",
91117
"signature": "type callback = SomeInnerModule.status => unit",
92-
"docstrings": ["Testing what this looks like."]
118+
"docstrings": ["Testing what this looks like."],
119+
"linkables": [{
120+
"path": "SomeInnerModule.status",
121+
"moduleName": "DocExtractionRes",
122+
"external": false
123+
}]
124+
},
125+
{
126+
"kind": "value",
127+
"name": "isGoodStatus",
128+
"signature": "let isGoodStatus: SomeInnerModule.status => bool",
129+
"docstrings": [],
130+
"linkables": [{
131+
"path": "SomeInnerModule.status",
132+
"moduleName": "DocExtractionRes",
133+
"external": false
134+
}]
135+
},
136+
{
137+
"kind": "type",
138+
"name": "someVariantWithInlineRecords",
139+
"signature": "type someVariantWithInlineRecords = SomeStuff({offline: bool})",
140+
"docstrings": ["Trying how it looks with an inline record in a variant."],
141+
"linkables": [],
142+
"detail":
143+
{
144+
"kind": "variant",
145+
"fieldDocs": [
146+
{
147+
"constructorName": "SomeStuff",
148+
"docstrings": ["This has inline records..."]
149+
}]
150+
}
151+
},
152+
{
153+
"kind": "type",
154+
"name": "domRoot",
155+
"signature": "type domRoot = unit => ReactDOM.Client.Root.t",
156+
"docstrings": ["Callback to get the DOM root..."],
157+
"linkables": [{
158+
"path": "ReactDOM.Client.Root.t",
159+
"moduleName": "ReactDOM",
160+
"external": true
161+
}]
93162
}]
94163
}
95164
}]

0 commit comments

Comments
 (0)