diff --git a/tools/bin/main.ml b/tools/bin/main.ml index 06be383b1..61d0ba764 100644 --- a/tools/bin/main.ml +++ b/tools/bin/main.ml @@ -14,7 +14,8 @@ Usage: rescript-tools [command] Commands: -doc Generate documentation +doc Generate documentation +dump Dump the TAST of a file reanalyze Reanalyze -v, --version Print version -h, --help Print help|} @@ -31,6 +32,7 @@ let version = Version.version let main () = match Sys.argv |> Array.to_list |> List.tl with + | ["dump"; file] -> Tools.dump file |> logAndExit | "doc" :: rest -> ( match rest with | ["-h"] | ["--help"] -> logAndExit (Ok docHelp) 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/prettier_printer.ml b/tools/src/prettier_printer.ml new file mode 100644 index 000000000..a19abe784 --- /dev/null +++ b/tools/src/prettier_printer.ml @@ -0,0 +1,297 @@ +module DSL = struct + type namedField = {name: string; value: oak} + + and oak = + | Application of string * oak + | Record of namedField list + | Ident of string + | Tuple of namedField list + | List of oak list + | String of string +end + +(** Transform the Oak types to string *) +module CodePrinter = struct + open DSL + + (** + The idea is that we capture events in a context type. + Doing this allows us to reason about the current state of the writer + and whether the next expression fits on the current line or not. + *) + + type writerEvents = + | Write of string + | WriteLine + | IndentBy of int + | UnindentBy of int + + type writerMode = Standard | TrySingleLine | ConfirmedMultiline + + (* Type representing the writer context during code printing + + - [indent_size] is the configured indentation size, typically 2 + - [max_line_length] is the maximum line length before we break the line + - [current_indent] is the current indentation size + - [current_line_column] is the characters written on the current line + - [line_count] is the number of lines written + - [events] is the write events in reverse order, head event is last written + - [mode] is the current writer mode (Standard or SingleLine) + *) + type context = { + indent_size: int; + max_line_length: int; + current_indent: int; + current_line_column: int; + line_count: int; + events: writerEvents list; + mode: writerMode; + } + + type appendEvents = context -> context + + let emptyContext = + { + indent_size = 2; + max_line_length = 120; + current_indent = 0; + current_line_column = 0; + line_count = 0; + events = []; + mode = Standard; + } + + (** Fold all the events in context into text *) + let dump (ctx : context) = + let buf = Buffer.create 1024 in + let addSpaces n = Buffer.add_string buf (String.make n ' ') in + + List.fold_right + (fun event current_indent -> + match event with + | Write str -> + Buffer.add_string buf str; + current_indent + | WriteLine -> + Buffer.add_char buf '\n'; + addSpaces current_indent; + current_indent + | IndentBy n -> current_indent + n + | UnindentBy n -> current_indent - n) + ctx.events ctx.current_indent + |> ignore; + Buffer.contents buf + + let debug_context (ctx : context) = + let mode = + match ctx.mode with + | Standard -> "Standard" + | TrySingleLine -> "TrySingleLine" + | ConfirmedMultiline -> "ConfirmedMultiline" + in + Format.printf + "Current indent: %d, Current column: %d, # Lines: %d Events: %d, Mode: %s\n" + ctx.current_indent ctx.current_line_column ctx.line_count + (List.length ctx.events) mode; + ctx + + let updateMode (newlineWasAdded : bool) (ctx : context) = + match ctx.mode with + | Standard -> ctx + | ConfirmedMultiline -> ctx + | TrySingleLine -> + { + ctx with + mode = + (if newlineWasAdded || ctx.current_line_column > ctx.max_line_length + then ConfirmedMultiline + else TrySingleLine); + } + + let id x = x + + (** add a write event to the context *) + let write str ctx = + { + ctx with + events = Write str :: ctx.events; + current_line_column = ctx.current_line_column + String.length str; + } + |> updateMode false + + (** compose two context transforming functions *) + let compose_aux f g ctx = + let fCtx = f ctx in + match fCtx.mode with + | ConfirmedMultiline -> fCtx + | _ -> g fCtx + + let compose (fs : appendEvents list) ctx = + let rec visit fs = + match fs with + | [] -> id + | [f] -> f + | f :: g :: rest -> visit (compose_aux f g :: rest) + in + visit fs ctx + + let sepNln ctx = + { + ctx with + events = WriteLine :: ctx.events; + current_line_column = ctx.current_indent; + line_count = ctx.line_count + 1; + } + |> updateMode true + + let sepSpace ctx = write " " ctx + let sepComma ctx = write ", " ctx + let sepSemi ctx = write "; " ctx + let sepOpenT ctx = write "(" ctx + let sepCloseT ctx = write ")" ctx + let sepOpenR ctx = write "{" ctx + let sepCloseR ctx = write "}" ctx + let sepOpenL ctx = write "[" ctx + let sepCloseL ctx = write "]" ctx + let sepEq ctx = write " = " ctx + let wrapInParentheses f = compose [sepOpenT; f; sepCloseT] + let indent ctx = + let nextIdent = ctx.current_indent + ctx.indent_size in + { + ctx with + current_indent = nextIdent; + current_line_column = nextIdent; + events = IndentBy ctx.indent_size :: ctx.events; + } + let unindent ctx = + let nextIdent = ctx.current_indent - ctx.indent_size in + { + ctx with + current_indent = nextIdent; + current_line_column = nextIdent; + events = UnindentBy ctx.indent_size :: ctx.events; + } + + let indentAndNln f = compose [indent; sepNln; f; unindent] + + let col (f : 't -> appendEvents) (intertwine : appendEvents) items ctx = + let rec visit items ctx = + match items with + | [] -> ctx + | [item] -> f item ctx + | item :: rest -> + let ctx' = compose [f item; intertwine] ctx in + visit rest ctx' + in + visit items ctx + + let expressionFitsOnRestOfLine (f : appendEvents) (fallback : appendEvents) + (ctx : context) = + match ctx.mode with + | ConfirmedMultiline -> ctx + | _ -> ( + let shortCtx = + match ctx.mode with + | Standard -> {ctx with mode = TrySingleLine} + | _ -> ctx + in + let resultCtx = f shortCtx in + match resultCtx.mode with + | ConfirmedMultiline -> fallback ctx + | TrySingleLine -> {resultCtx with mode = ctx.mode} + | Standard -> + failwith "Unexpected Standard mode after trying SingleLine mode") + + let rec genOak (oak : oak) : appendEvents = + match oak with + | Application (name, argument) -> genApplication name argument + | Record record -> genRecord record + | Ident ident -> genIdent ident + | String str -> write (Format.sprintf "\"%s\"" str) + | Tuple ts -> genTuple ts + | List xs -> genList xs + + and genApplication (name : string) (argument : oak) : appendEvents = + let short = compose [write name; sepOpenT; genOak argument; sepCloseT] in + let long = + compose + [ + write name; + sepOpenT; + (match argument with + | List _ | Record _ -> genOak argument + | _ -> compose [indentAndNln (genOak argument); sepNln]); + sepCloseT; + ] + in + expressionFitsOnRestOfLine short long + + and genRecord (recordFields : namedField list) : appendEvents = + let short = + match recordFields with + | [] -> compose [sepOpenR; sepCloseR] + | fields -> + compose + [ + sepOpenR; + sepSpace; + col genNamedField sepSemi fields; + sepSpace; + sepCloseR; + ] + in + let long = + compose + [ + sepOpenR; + indentAndNln (col genNamedField sepNln recordFields); + sepNln; + sepCloseR; + ] + in + expressionFitsOnRestOfLine short long + + and genTuple (oaks : namedField list) : appendEvents = + let short = col genNamedField sepComma oaks in + let long = col genNamedField sepNln oaks in + expressionFitsOnRestOfLine short long + + and genIdent (ident : string) : appendEvents = write ident + + and genNamedField (field : namedField) : appendEvents = + let genValue = + match field.value with + | Tuple _ -> compose [sepOpenT; genOak field.value; sepCloseT] + | _ -> genOak field.value + in + let short = compose [write field.name; sepEq; genValue] in + let long = + compose + [ + write field.name; + sepEq; + (match field.value with + | List _ | Record _ -> genOak field.value + | _ -> indentAndNln genValue); + ] + in + expressionFitsOnRestOfLine short long + + and genList (items : oak list) : appendEvents = + let genItem = function + | Tuple _ as item -> wrapInParentheses (genOak item) + | item -> genOak item + in + let short = + match items with + | [] -> compose [sepOpenL; sepCloseL] + | _ -> + compose + [sepOpenL; sepSpace; col genItem sepSemi items; sepSpace; sepCloseL] + in + let long = + compose + [sepOpenL; indentAndNln (col genItem sepNln items); sepNln; sepCloseL] + in + expressionFitsOnRestOfLine short long +end diff --git a/tools/src/print_tast.ml b/tools/src/print_tast.ml new file mode 100644 index 000000000..4f8b1e997 --- /dev/null +++ b/tools/src/print_tast.ml @@ -0,0 +1,499 @@ +open Prettier_printer +open DSL +open Analysis + +module Transform = struct + let mk_bool (b : bool) : oak = if b then Ident "true" else Ident "false" + + let mk_option f o = + match o with + | None -> Ident "None" + | Some x -> Application ("Some", f x) + + let mk_string_option (o : string option) : oak = + mk_option (fun s -> String s) o + + let mk_list f l = List (List.map f l) + + let mk_string_list (items : string list) : oak = + mk_list (fun s -> String s) items + + let mk_int_list (items : int list) : oak = + mk_list (fun i -> Ident (string_of_int i)) items + + 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 mk_path path = Ident (path_to_string path) + + let mk_row_field (row_field : Types.row_field) : oak = + match row_field with + | Rpresent _ -> Ident "row_field.Rpresent" + | Reither _ -> Ident "row_field.Reither" + | Rabsent -> Ident "row_field.Rabsent" + + let mk_field_kind = function + | Types.Fvar _ -> Ident "field_kind.Fvar" + | Types.Fpresent -> Ident "field_kind.Fpresent" + | Types.Fabsent -> Ident "field_kind.Fabsent" + + let rec mk_type_desc (desc : Types.type_desc) : oak = + match desc with + | Tlink {desc} -> Application ("type_desc.Tlink", mk_type_desc desc) + | Tvar var -> Application ("type_desc.Tvar", mk_string_option var) + | Tconstr (path, ts, _) -> + Application + ( "type_desc.Tconstr", + Tuple + [ + {name = "path"; value = mk_path path}; + {name = "ts"; value = mk_type_expr_list ts}; + ] ) + | Tarrow (_, t1, t2, _) -> + Application + ( "type_desc.Tarrow", + Tuple + [ + {name = "t1"; value = mk_type_desc t1.desc}; + {name = "t2"; value = mk_type_desc t2.desc}; + ] ) + | Ttuple ts -> Application ("type_desc.Ttuple", mk_type_expr_list ts) + | Tobject (t, r) -> ( + match !r with + | None -> Application ("type_desc.Tobject", mk_type_desc t.desc) + | Some (path, ts) -> + Application + ( "type_desc.Tobject", + Tuple + [ + {name = "type_expr"; value = mk_type_desc t.desc}; + {name = "path"; value = mk_path path}; + { + name = "ts"; + value = + List + (ts + |> List.map (fun (t : Types.type_expr) -> + mk_type_desc t.desc)); + }; + ] )) + | Tfield (field, fk, t1, t2) -> + Application + ( "type_desc.Tfield", + Tuple + [ + {name = "name"; value = String field}; + {name = "field_kind"; value = mk_field_kind fk}; + {name = "t1"; value = mk_type_desc t1.desc}; + {name = "t2"; value = mk_type_desc t2.desc}; + ] ) + | Tnil -> Ident "type_desc.Tnil" + | Tsubst t -> Application ("type_desc.Tsubst", mk_type_desc t.desc) + | Tvariant row_descr -> + Application ("type_desc.Tvariant", mk_row_desc row_descr) + | Tunivar so -> Application ("type_desc.Tunivar", mk_string_option so) + | Tpoly (t, ts) -> + Application + ( "type_desc.Tpoly", + Tuple + [ + {name = "t"; value = mk_type_desc t.desc}; + {name = "ts"; value = mk_type_expr_list ts}; + ] ) + | Tpackage (path, lids, ts) -> + let lids = + lids + |> List.map (fun (lid : Longident.t) -> + List + (Longident.flatten lid |> List.map (fun ident -> String ident))) + in + Application + ( "type_desc.Tpackage", + Tuple + [ + {name = "path"; value = mk_path path}; + {name = "lids"; value = List lids}; + {name = "ts"; value = mk_type_expr_list ts}; + ] ) + + and mk_row_desc (row_desc : Types.row_desc) : oak = + let fields = + [ + { + name = "row_fields"; + value = + ( row_desc.row_fields + |> List.map (fun (label, row_field) -> + Tuple + [ + {name = "label"; value = Ident label}; + {name = "row_field"; value = mk_row_field row_field}; + ]) + |> fun ts -> List ts ); + }; + {name = "row_more"; value = mk_type_desc row_desc.row_more.desc}; + {name = "row_closed"; value = mk_bool row_desc.row_closed}; + {name = "row_fixed"; value = mk_bool row_desc.row_fixed}; + ] + in + match row_desc.row_name with + | None -> Record fields + | Some (path, ts) -> + Record + ({ + name = "row_name"; + value = + Tuple + [ + {name = "Path.t"; value = mk_path path}; + { + name = "fields"; + value = + List + (ts + |> List.map (fun (t : Types.type_expr) -> + mk_type_desc t.desc)); + }; + ]; + } + :: fields) + + and mk_type_expr_list ts = + List (List.map (fun (t : Types.type_expr) -> mk_type_desc t.desc) ts) + + let mk_FileSet (fileSet : SharedTypes.FileSet.t) : oak = + List (fileSet |> SharedTypes.FileSet.to_list |> List.map (fun s -> String s)) + + let mk_builtInCompletionModules + (builtInCompletionModules : SharedTypes.builtInCompletionModules) : oak = + Record + [ + { + name = "arrayModulePath"; + value = mk_string_list builtInCompletionModules.arrayModulePath; + }; + { + name = "optionModulePath"; + value = mk_string_list builtInCompletionModules.optionModulePath; + }; + { + name = "stringModulePath"; + value = mk_string_list builtInCompletionModules.stringModulePath; + }; + { + name = "intModulePath"; + value = mk_string_list builtInCompletionModules.intModulePath; + }; + { + name = "floatModulePath"; + value = mk_string_list builtInCompletionModules.floatModulePath; + }; + { + name = "promiseModulePath"; + value = mk_string_list builtInCompletionModules.promiseModulePath; + }; + { + name = "listModulePath"; + value = mk_string_list builtInCompletionModules.listModulePath; + }; + { + name = "resultModulePath"; + value = mk_string_list builtInCompletionModules.resultModulePath; + }; + { + name = "exnModulePath"; + value = mk_string_list builtInCompletionModules.exnModulePath; + }; + { + name = "regexpModulePath"; + value = mk_string_list builtInCompletionModules.regexpModulePath; + }; + ] + + let mk_package (package : SharedTypes.package) : oak = + Record + [ + { + name = "genericJsxModule"; + value = mk_string_option package.genericJsxModule; + }; + {name = "suffix"; value = String package.suffix}; + {name = "rootPath"; value = String package.rootPath}; + {name = "projectFiles"; value = mk_FileSet package.projectFiles}; + { + name = "dependenciesFiles"; + value = mk_FileSet package.dependenciesFiles; + }; + {name = "namespace"; value = mk_string_option package.namespace}; + { + name = "builtInCompletionModules"; + value = mk_builtInCompletionModules package.builtInCompletionModules; + }; + {name = "opens"; value = mk_string_list (List.concat package.opens)}; + {name = "uncurried"; value = mk_bool package.uncurried}; + { + name = "rescriptVersion"; + value = + (let major, minor = package.rescriptVersion in + Tuple + [ + {name = "major"; value = String (string_of_int major)}; + {name = "minor"; value = String (string_of_int minor)}; + ]); + }; + ] + + let mk_Uri (uri : Uri.t) : oak = String (Uri.toString uri) + + let mk_rec_status = function + | Types.Trec_not -> Ident "Trec_not" + | Types.Trec_first -> Ident "Trec_first" + | Types.Trec_next -> Ident "Trec_next" + + let mk_field (field : SharedTypes.field) : oak = + Record + [ + {name = "stamp"; value = Ident (string_of_int field.stamp)}; + {name = "fname"; value = String field.fname.txt}; + {name = "typ"; value = mk_type_desc field.typ.desc}; + {name = "optional"; value = mk_bool field.optional}; + {name = "docstring"; value = mk_string_list field.docstring}; + {name = "deprecated"; value = mk_string_option field.deprecated}; + ] + + let mk_pos (pos : Lexing.position) : oak = + Record + [ + {name = "pos_fname"; value = String pos.pos_fname}; + {name = "pos_lnum"; value = Ident (string_of_int pos.pos_lnum)}; + {name = "pos_bol"; value = Ident (string_of_int pos.pos_bol)}; + {name = "pos_cnum"; value = Ident (string_of_int pos.pos_cnum)}; + ] + + let mk_location (loc : Location.t) = + Record + [ + {name = "loc_start"; value = mk_pos loc.loc_start}; + {name = "loc_end"; value = mk_pos loc.loc_end}; + {name = "loc_ghost"; value = mk_bool loc.loc_ghost}; + ] + + let mk_string_loc (loc : string Location.loc) : oak = + Record + [ + {name = "txt"; value = String loc.txt}; + {name = "loc"; value = mk_location loc.loc}; + ] + + let mk_constructor_args (args : SharedTypes.constructorArgs) : oak = + match args with + | SharedTypes.InlineRecord fields -> + Application + ("constructorArgs.InlineRecord", List (fields |> List.map mk_field)) + | SharedTypes.Args ts -> + let ts = + ts + |> List.map (fun ((t : Types.type_expr), loc) -> + Tuple + [ + {name = "type"; value = mk_type_desc t.desc}; + {name = "loc"; value = mk_location loc}; + ]) + in + Application ("constructorArgs.Tuple", List ts) + + let mk_constructor (ctor : SharedTypes.Constructor.t) : oak = + Record + [ + {name = "stamp"; value = Ident (string_of_int ctor.stamp)}; + { + name = "cname"; + value = + Record + [ + {name = "txt"; value = String ctor.cname.txt}; + {name = "loc"; value = mk_location ctor.cname.loc}; + ]; + }; + {name = "args"; value = mk_constructor_args ctor.args}; + {name = "docstring"; value = mk_string_list ctor.docstring}; + {name = "deprecated"; value = mk_string_option ctor.deprecated}; + ] + let mk_attribute_payload (payload : Parsetree.payload) : oak = + match payload with + | PStr _ -> Ident "payload.PStr" + | PSig _ -> Ident "payload.PSig" + | PTyp _ -> Ident "payload.PTyp" + | PPat _ -> Ident "payload.PPat" + + let mk_attribute (attribute : Parsetree.attribute) : oak = + let loc, payload = attribute in + Tuple + [ + {name = "loc"; value = mk_string_loc loc}; + {name = "payload"; value = mk_attribute_payload payload}; + ] + + let mk_attribute_list (attributes : Parsetree.attributes) = + List (attributes |> List.map mk_attribute) + + let mk_type_kind (kind : SharedTypes.Type.kind) : oak = + match kind with + | SharedTypes.Type.Abstract _ -> Ident "Type.kind.Abstract" + | SharedTypes.Type.Open -> Ident "Type.kind.Open" + | SharedTypes.Type.Tuple ts -> + Application ("Type.kind.Tuple", mk_type_expr_list ts) + | SharedTypes.Type.Record fields -> + let fields = List.map mk_field fields in + Application ("Type.kind.Record", List fields) + | SharedTypes.Type.Variant ctors -> + Application ("Type.kind.Variant", List (ctors |> List.map mk_constructor)) + + let mk_type_declaration_type_kind (type_kind : Types.type_kind) : oak = + match type_kind with + | Type_abstract -> Ident "type_kind.Type_abstract" + | Type_variant _ -> Ident "type_kind.Type_variant" + | Type_record _ -> Ident "type_kind.Type_record" + | Type_open -> Ident "type_kind.Type_open" + + let mk_private_flag = function + | Asttypes.Private -> Ident "Private" + | Asttypes.Public -> Ident "Public" + + let mk_unboxed_status (status : Types.unboxed_status) : oak = + Record + [ + {name = "unboxed"; value = mk_bool status.unboxed}; + {name = "default"; value = mk_bool status.default}; + ] + + let mk_type_declaration (td : Types.type_declaration) : oak = + Record + [ + {name = "type_params"; value = mk_type_expr_list td.type_params}; + {name = "type_arity"; value = Ident (string_of_int td.type_arity)}; + {name = "type_kind"; value = mk_type_declaration_type_kind td.type_kind}; + {name = "type_private"; value = mk_private_flag td.type_private}; + { + name = "type_manifest"; + value = + mk_option + (fun (te : Types.type_expr) -> mk_type_desc te.desc) + td.type_manifest; + }; + { + name = "type_newtype_level"; + value = + mk_option + (fun (i1, i2) -> + Tuple + [ + {name = "i1"; value = Ident (string_of_int i1)}; + {name = "i2"; value = Ident (string_of_int i2)}; + ]) + td.type_newtype_level; + }; + {name = "type_loc"; value = mk_location td.type_loc}; + {name = "type_attributes"; value = mk_attribute_list td.type_attributes}; + {name = "type_immediate"; value = mk_bool td.type_immediate}; + {name = "type_unboxed"; value = mk_unboxed_status td.type_unboxed}; + ] + + let mk_type (type_ : SharedTypes.Type.t) : oak = + Record + [ + {name = "kind"; value = mk_type_kind type_.kind}; + {name = "decl"; value = mk_type_declaration type_.decl}; + {name = "name"; value = String type_.name}; + {name = "attributes"; value = mk_attribute_list type_.attributes}; + ] + let rec mk_structure (structure : SharedTypes.Module.structure) : oak = + Record + [ + {name = "name"; value = String structure.name}; + {name = "docstring"; value = mk_string_list structure.docstring}; + {name = "items"; value = List (List.map mk_item structure.items)}; + {name = "deprecated"; value = mk_string_option structure.deprecated}; + ] + + and mk_module (module_ : SharedTypes.Module.t) : oak = + match module_ with + | SharedTypes.Module.Ident path -> Application ("Ident", mk_path path) + | SharedTypes.Module.Structure structure -> + Application ("Structure", mk_structure structure) + | SharedTypes.Module.Constraint (t1, t2) -> + Application + ( "Constraint", + Tuple + [ + {name = "t1"; value = mk_module t1}; + {name = "t2"; value = mk_module t2}; + ] ) + + and mk_item (item : SharedTypes.Module.item) : oak = + let kind = + match item.kind with + | SharedTypes.Module.Value v -> + Application ("SharedTypes.Module.Value", mk_type_desc v.desc) + | SharedTypes.Module.Type (t, rec_status) -> + Application + ( "Type", + Tuple + [ + {name = "type"; value = mk_type t}; + {name = "rec_status"; value = mk_rec_status rec_status}; + ] ) + | SharedTypes.Module.Module m -> + Application + ( "Module", + Record + [ + {name = "type_"; value = mk_module m.type_}; + {name = "isModuleType"; value = mk_bool m.isModuleType}; + ] ) + in + Record + [ + {name = "name"; value = String item.name}; + {name = "kind"; value = kind}; + {name = "docstring"; value = mk_string_list item.docstring}; + {name = "deprecated"; value = mk_string_option item.deprecated}; + ] + + let mk_file (file : SharedTypes.File.t) : oak = + Record + [ + {name = "uri"; value = mk_Uri file.uri}; + {name = "moduleName"; value = String file.moduleName}; + {name = "structure"; value = mk_structure file.structure}; + ] + + let mk_full (full : SharedTypes.full) : oak = + Record + [ + {name = "package"; value = mk_package full.package}; + {name = "file"; value = mk_file full.file}; + ] +end + +let print_type_expr (typ : Types.type_expr) : string = + CodePrinter.genOak (Transform.mk_type_desc typ.desc) CodePrinter.emptyContext + |> CodePrinter.dump + +let print_full (full : SharedTypes.full) : string = + CodePrinter.genOak (Transform.mk_full full) CodePrinter.emptyContext + |> CodePrinter.dump diff --git a/tools/src/tools.ml b/tools/src/tools.ml index 08837943b..d24be8387 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,43 @@ 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 valueDetail (typ : Types.type_expr) = + let rec collectSignatureTypes (typ_desc : Types.type_desc) = + match typ_desc with + | Tlink t -> collectSignatureTypes t.desc + | Tconstr (Path.Pident {name = "function$"}, [t; _], _) -> + collectSignatureTypes t.desc + | Tconstr (path, ts, _) -> ( + let p = Print_tast.Transform.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 +477,7 @@ let extractDocs ~entryPointFile ~debug = ^ Shared.typeToString typ; name = item.name; deprecated = item.deprecated; + detail = valueDetail typ; source; }) | Type (typ, _) -> @@ -561,3 +641,28 @@ let extractEmbedded ~extensionPoints ~filename = ("loc", Some (Analysis.Utils.cmtLocToRange loc |> stringifyRange)); ]) |> List.rev |> array + +(** Dump the contents of a typed tree file *) +let dump entryPointFile = + let path = + if Filename.is_relative entryPointFile then Unix.realpath entryPointFile + else entryPointFile + in + let result = + if + FindFiles.isImplementation path = false + && FindFiles.isInterface path = false + then + Error + (Printf.sprintf + "error: failed to read %s, expected an .res or .resi file" path) + else + match Cmt.loadFullCmtFromPath ~path with + | None -> + Error + (Printf.sprintf + "error: failed to dump for %s, try to build the project" path) + | Some full -> Ok (Print_tast.print_full full) + in + + result 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" + } + } } }] }]