From fdaf766362512e177758b4acb082872f410ab98c Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 17 Jun 2026 12:40:46 -0300 Subject: [PATCH 1/6] Omit empty document symbol children DocumentSymbol.children is optional, so only set it when a symbol has nested entries. This avoids serializing leaf symbols with an empty children array. Signed-off-by: Pedro Castro --- analysis/src/document_symbol.ml | 19 ++++++++++--------- .../tests/src/expected/DocumentSymbol.res.txt | 11 ----------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/analysis/src/document_symbol.ml b/analysis/src/document_symbol.ml index 5fec0db5d78..8a70389eb9b 100644 --- a/analysis/src/document_symbol.ml +++ b/analysis/src/document_symbol.ml @@ -10,8 +10,8 @@ let get_symbols ~source ~kind_file = then let range = Utils.cmt_loc_to_range loc in let symbol = - Lsp.Types.DocumentSymbol.create ~name ~range ~selectionRange:range - ~children:[] ~kind () + Lsp.Types.DocumentSymbol.create ~name ~range ~selectionRange:range ~kind + () in symbols := symbol :: !symbols in @@ -165,13 +165,14 @@ let get_symbols ~source ~kind_file = | [] -> [symbol] | last :: rest -> if is_inside symbol last then - match last.children with - | Some c -> - let new_last = - {last with children = Some (c |> add_symbol_to_children ~symbol)} - in - new_last :: rest - | _ -> rest + let children = last.children |> Option.value ~default:[] in + let new_last = + { + last with + children = Some (children |> add_symbol_to_children ~symbol); + } + in + new_last :: rest else symbol :: children in let rec add_sorted_symbols_to_children ~sorted_symbols children = diff --git a/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt b/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt index 48eb87b68fb..73f0bf9244f 100644 --- a/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt +++ b/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt @@ -1,7 +1,6 @@ DocumentSymbol src/DocumentSymbol.res [ { - "children": [], "kind": 16, "name": "zzz", "range": { @@ -18,7 +17,6 @@ DocumentSymbol src/DocumentSymbol.res { "children": [ { - "children": [], "kind": 12, "name": "make", "range": { @@ -55,7 +53,6 @@ DocumentSymbol src/DocumentSymbol.res } }, { - "children": [], "kind": 16, "name": "fa", "range": { @@ -70,7 +67,6 @@ DocumentSymbol src/DocumentSymbol.res { "children": [ { - "children": [], "kind": 12, "name": "abd", "range": { @@ -83,7 +79,6 @@ DocumentSymbol src/DocumentSymbol.res } }, { - "children": [], "kind": 12, "name": "abc", "range": { @@ -96,7 +91,6 @@ DocumentSymbol src/DocumentSymbol.res } }, { - "children": [], "kind": 26, "name": "t", "range": { @@ -121,7 +115,6 @@ DocumentSymbol src/DocumentSymbol.res } }, { - "children": [], "kind": 13, "name": "op", "range": { @@ -136,7 +129,6 @@ DocumentSymbol src/DocumentSymbol.res { "children": [ { - "children": [], "kind": 12, "name": "next", "range": { @@ -149,7 +141,6 @@ DocumentSymbol src/DocumentSymbol.res } }, { - "children": [], "kind": 12, "name": "foo", "range": { @@ -176,7 +167,6 @@ DocumentSymbol src/DocumentSymbol.res { "children": [ { - "children": [], "kind": 12, "name": "customDouble", "range": { @@ -201,7 +191,6 @@ DocumentSymbol src/DocumentSymbol.res } }, { - "children": [], "kind": 2, "name": "MyList", "range": { From b5f3b72ed994c3ad702b69e077aa12261726fa11 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sat, 13 Jun 2026 09:27:21 -0300 Subject: [PATCH 2/6] add transform_opt for server-side use --- analysis/src/codemod.ml | 90 ++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/analysis/src/codemod.ml b/analysis/src/codemod.ml index 9b0812dd9a7..ce16e2f2592 100644 --- a/analysis/src/codemod.ml +++ b/analysis/src/codemod.ml @@ -5,43 +5,57 @@ let rec collect_patterns p = | Ppat_or (p1, p2) -> collect_patterns p1 @ [p2] | _ -> [p] -let transform ~source ~pos ~debug ~typ ~hint = - let structure, print_expr, _, _ = Xform.parse_implementation ~source in - match typ with - | AddMissingCases -> ( - let source = "let " ^ hint ^ " = ()" in - let {Res_driver.parsetree = hint_structure} = - Res_driver.parse_implementation_from_source ~for_printer:false - ~display_filename:"" ~source - in - match hint_structure with - | [{pstr_desc = Pstr_value (_, [{pvb_pat = pattern}])}] -> ( - let cases = - collect_patterns pattern - |> List.map (fun (p : Parsetree.pattern) -> - Ast_helper.Exp.case p (Type_utils.Codegen.mk_fail_with_exp ())) +let transform_opt ~source ~pos ~debug ~typ ~hint = + let log message = if debug then print_endline message in + try + let structure, print_expr, _, _ = Xform.parse_implementation ~source in + match typ with + | AddMissingCases -> ( + let source = "let " ^ hint ^ " = ()" in + let {Res_driver.parsetree = hint_structure} = + Res_driver.parse_implementation_from_source ~for_printer:false + ~display_filename:"" ~source in - let result = ref None in - let mk_iterator ~pos ~result = - let expr (iterator : Ast_iterator.iterator) (exp : Parsetree.expression) - = - match exp.pexp_desc with - | Pexp_match (e, existing_cases) - when Pos.of_lexing exp.pexp_loc.loc_start = pos -> - result := - Some {exp with pexp_desc = Pexp_match (e, existing_cases @ cases)} - | _ -> Ast_iterator.default_iterator.expr iterator exp + match hint_structure with + | [{pstr_desc = Pstr_value (_, [{pvb_pat = pattern}])}] -> ( + let cases = + collect_patterns pattern + |> List.map (fun (p : Parsetree.pattern) -> + Ast_helper.Exp.case p (Type_utils.Codegen.mk_fail_with_exp ())) in - {Ast_iterator.default_iterator with expr} - in - let iterator = mk_iterator ~pos ~result in - iterator.structure iterator structure; - match !result with - | None -> - if debug then print_endline "Found no result"; - exit 1 - | Some switch_expr -> - print_expr ~range:(Loc.range_of_loc switch_expr.pexp_loc) switch_expr) - | _ -> - if debug then print_endline "Mismatch in expected structure"; - exit 1) + let result = ref None in + let mk_iterator ~pos ~result = + let expr (iterator : Ast_iterator.iterator) + (exp : Parsetree.expression) = + match exp.pexp_desc with + | Pexp_match (e, existing_cases) + when Pos.of_lexing exp.pexp_loc.loc_start = pos -> + result := + Some + {exp with pexp_desc = Pexp_match (e, existing_cases @ cases)} + | _ -> Ast_iterator.default_iterator.expr iterator exp + in + {Ast_iterator.default_iterator with expr} + in + let iterator = mk_iterator ~pos ~result in + iterator.structure iterator structure; + match !result with + | None -> + log "Found no result"; + None + | Some switch_expr -> + Some + (print_expr + ~range:(Loc.range_of_loc switch_expr.pexp_loc) + switch_expr)) + | _ -> + log "Mismatch in expected structure"; + None) + with exn -> + log ("Codemod failed: " ^ Printexc.to_string exn); + None + +let transform ~source ~pos ~debug ~typ ~hint = + match transform_opt ~source ~pos ~debug ~typ ~hint with + | Some result -> result + | None -> exit 1 From 1cab9648e7db62643647052f26aaa3c5b68b62a1 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Mon, 8 Jun 2026 20:26:48 -0300 Subject: [PATCH 3/6] analysis: add state_to_yojson --- analysis/src/shared_types.ml | 103 +++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/analysis/src/shared_types.ml b/analysis/src/shared_types.ml index 173a6bfb7a7..ddecce94f10 100644 --- a/analysis/src/shared_types.ml +++ b/analysis/src/shared_types.ml @@ -965,3 +965,106 @@ let extract_exp_apply_args ~args = | [] -> List.rev acc in args |> process_args ~acc:[] + +let state_to_yojson (state : state) = + let option_to_yojson f = function + | None -> `Null + | Some value -> f value + in + + let string_set_to_yojson set = + `List (set |> File_set.elements |> List.map (fun value -> `String value)) + in + + let path_to_yojson path = `List (List.map (fun item -> `String item) path) in + + let paths_to_yojson = function + | Impl {cmt; res} -> + `Assoc + [("kind", `String "Impl"); ("cmt", `String cmt); ("res", `String res)] + | Namespace {cmt} -> + `Assoc [("kind", `String "Namespace"); ("cmt", `String cmt)] + | IntfAndImpl {cmti; resi; cmt; res} -> + `Assoc + [ + ("kind", `String "IntfAndImpl"); + ("cmti", `String cmti); + ("resi", `String resi); + ("cmt", `String cmt); + ("res", `String res); + ] + in + + let paths_for_module_to_yojson paths_for_module = + paths_for_module |> Hashtbl.to_seq + |> Seq.map (fun (file, paths) -> (file, paths_to_yojson paths)) + |> List.of_seq + |> fun fields -> `Assoc fields + in + + let autocomplete_to_yojson autocomplete = + autocomplete |> Misc.String_map.bindings + |> List.map (fun (name, files) -> + (name, `List (List.map (fun file -> `String file) files))) + |> fun fields -> `Assoc fields + in + + let package_to_yojson (package : package) = + let major, minor = package.rescript_version in + `Assoc + [ + ( "generic_jsx_module", + option_to_yojson + (fun value -> `String value) + package.generic_jsx_module ); + ("suffix", `String package.suffix); + ("root_path", `String package.root_path); + ("project_files", string_set_to_yojson package.project_files); + ("dependencies_files", string_set_to_yojson package.dependencies_files); + ("paths_for_module", paths_for_module_to_yojson package.paths_for_module); + ( "namespace", + option_to_yojson (fun value -> `String value) package.namespace ); + ("opens", `List (List.map path_to_yojson package.opens)); + ( "rescript_version", + `Assoc [("major", `Int major); ("minor", `Int minor)] ); + ("autocomplete", autocomplete_to_yojson package.autocomplete); + ] + in + + let file_to_yojson (file : File.t) = + `Assoc + [ + ("uri", `String (file.uri |> Lsp.Uri.to_string)); + ("module_name", `String file.module_name); + ("stamps_count", `Int (List.length (Stamps.get_entries file.stamps))); + ("structure_name", `String file.structure.name); + ( "structure_docstring", + `List (List.map (fun value -> `String value) file.structure.docstring) + ); + ("structure_items_count", `Int (List.length file.structure.items)); + ] + in + + let cmt_cache = + state.cmt_cache |> Hashtbl.to_seq + |> Seq.map (fun (file_path, file) -> (file_path, file_to_yojson file)) + |> List.of_seq + in + + let root_for_uri = + state.root_for_uri |> Hashtbl.to_seq |> List.of_seq + |> List.map (fun (uri, str) -> [(Lsp.Uri.to_string uri, `String str)]) + |> List.flatten + in + + let packages_by_root = + state.packages_by_root |> Hashtbl.to_seq |> List.of_seq + |> List.map (fun (root, package) -> (root, package_to_yojson package)) + in + + `Assoc + [ + ("cmt_cache", `Assoc cmt_cache); + ("root_for_uri", `Assoc root_for_uri); + ("packages_by_root", `Assoc packages_by_root); + ] From 98438198865654b17b69e566d465d87ff94b067d Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 10 Jun 2026 13:11:39 -0300 Subject: [PATCH 4/6] packages: add dependencies --- analysis/src/packages.ml | 11 +++++++++++ analysis/src/shared_types.ml | 1 + 2 files changed, 12 insertions(+) diff --git a/analysis/src/packages.ml b/analysis/src/packages.ml index 3c6d6d530d2..fd313e9acd1 100644 --- a/analysis/src/packages.ml +++ b/analysis/src/packages.ml @@ -172,12 +172,23 @@ let new_bs_package ~root_path = |> List.rev_append opens_from_compiler_flags |> List.map (fun path -> path @ ["place holder"]) in + let dependencies = + match config |> Yojson_helpers.get "dependencies" with + | Some (`List deps) -> + deps + |> List.filter_map (fun (x : Yojson.Safe.t) -> + match x with + | `String name -> Some name + | _ -> None) + | _ -> [] + in { generic_jsx_module; suffix; rescript_version; root_path; project_files; + dependencies; dependencies_files; paths_for_module; opens; diff --git a/analysis/src/shared_types.ml b/analysis/src/shared_types.ml index ddecce94f10..9f1a8aa8220 100644 --- a/analysis/src/shared_types.ml +++ b/analysis/src/shared_types.ml @@ -530,6 +530,7 @@ and package = { suffix: string; root_path: file_path; project_files: File_set.t; + dependencies: string list; dependencies_files: File_set.t; paths_for_module: (file, paths) Hashtbl.t; namespace: string option; From 09ca89a49614f1f3a34320af29630e495a06008a Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 17 Jun 2026 13:01:16 -0300 Subject: [PATCH 5/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7782095395..d22427401f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ - Refactor analysis CLI helpers to use source input. https://github.com/rescript-lang/rescript/pull/8466 - Include syntax, gentype, analysis, tools, and reanalyze tests in coverage reports. https://github.com/rescript-lang/rescript/pull/8467 - Remove the unreachable `Longident.Lapply` constructor (OCaml's applicative-functor path syntax `F(X).t`, which ReScript's grammar cannot produce). https://github.com/rescript-lang/rescript/pull/8469 +- Refactor analysis for server side use. https://github.com/rescript-lang/rescript/pull/8478 # 13.0.0-alpha.4 From 5ad1af7b191442aca945e2c7b176a833c98ee3bb Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 17 Jun 2026 13:11:19 -0300 Subject: [PATCH 6/6] Apply codex review suggestions --- analysis/src/packages.ml | 19 +++++++++++-------- analysis/src/shared_types.ml | 2 ++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/analysis/src/packages.ml b/analysis/src/packages.ml index fd313e9acd1..ed90107de7a 100644 --- a/analysis/src/packages.ml +++ b/analysis/src/packages.ml @@ -173,14 +173,17 @@ let new_bs_package ~root_path = |> List.map (fun path -> path @ ["place holder"]) in let dependencies = - match config |> Yojson_helpers.get "dependencies" with - | Some (`List deps) -> - deps - |> List.filter_map (fun (x : Yojson.Safe.t) -> - match x with - | `String name -> Some name - | _ -> None) - | _ -> [] + match + ( config + |> Yojson_helpers.get "dependencies" + |> bind Yojson_helpers.to_list_opt, + config + |> Yojson_helpers.get "bs-dependencies" + |> bind Yojson_helpers.to_list_opt ) + with + | None, None -> [] + | Some deps, None | _, Some deps -> + deps |> List.filter_map Yojson_helpers.string_opt in { generic_jsx_module; diff --git a/analysis/src/shared_types.ml b/analysis/src/shared_types.ml index 9f1a8aa8220..533baf09a88 100644 --- a/analysis/src/shared_types.ml +++ b/analysis/src/shared_types.ml @@ -1021,6 +1021,8 @@ let state_to_yojson (state : state) = ("suffix", `String package.suffix); ("root_path", `String package.root_path); ("project_files", string_set_to_yojson package.project_files); + ( "dependencies", + `List (package.dependencies |> List.map (fun x -> `String x)) ); ("dependencies_files", string_set_to_yojson package.dependencies_files); ("paths_for_module", paths_for_module_to_yojson package.paths_for_module); ( "namespace",