|
| 1 | +open Import |
| 2 | +module H = Ocaml_parsing.Ast_helper |
| 3 | + |
| 4 | +let range_contains_loc range loc = |
| 5 | + match Range.of_loc_opt loc with |
| 6 | + | Some range' -> Range.contains range range' |
| 7 | + | None -> false |
| 8 | + |
| 9 | +let range_contained_by_loc range loc = |
| 10 | + match Range.of_loc_opt loc with |
| 11 | + | Some range' -> Range.contains range' range |
| 12 | + | None -> false |
| 13 | + |
| 14 | +let largest_enclosed_expression typedtree range = |
| 15 | + let exception Found of Typedtree.expression in |
| 16 | + let module I = Ocaml_typing.Tast_iterator in |
| 17 | + let expr_iter (iter : I.iterator) (expr : Typedtree.expression) = |
| 18 | + if range_contains_loc range expr.exp_loc then raise (Found expr) |
| 19 | + else I.default_iterator.expr iter expr |
| 20 | + in |
| 21 | + let iterator = { I.default_iterator with expr = expr_iter } in |
| 22 | + try |
| 23 | + iterator.structure iterator typedtree; |
| 24 | + None |
| 25 | + with Found e -> Some e |
| 26 | + |
| 27 | +let enclosing_structure_item typedtree range = |
| 28 | + let exception Found of Typedtree.structure_item in |
| 29 | + let module I = Ocaml_typing.Tast_iterator in |
| 30 | + let structure_item_iter (iter : I.iterator) (item : Typedtree.structure_item) |
| 31 | + = |
| 32 | + if range_contained_by_loc range item.str_loc then |
| 33 | + match item.str_desc with |
| 34 | + | Tstr_value _ -> raise (Found item) |
| 35 | + | _ -> I.default_iterator.structure_item iter item |
| 36 | + in |
| 37 | + let iterator = |
| 38 | + { I.default_iterator with structure_item = structure_item_iter } |
| 39 | + in |
| 40 | + try |
| 41 | + iterator.structure iterator typedtree; |
| 42 | + None |
| 43 | + with Found e -> Some e |
| 44 | + |
| 45 | +let tightest_enclosing_binder_position typedtree range = |
| 46 | + let exception Found of Position.t in |
| 47 | + let module I = Ocaml_typing.Tast_iterator in |
| 48 | + let found_loc loc = |
| 49 | + Position.of_lexical_position loc |
| 50 | + |> Option.iter ~f:(fun p -> raise (Found p)) |
| 51 | + in |
| 52 | + let found_if_expr_contains (expr : Typedtree.expression) = |
| 53 | + let loc = expr.exp_loc in |
| 54 | + if range_contained_by_loc range loc then found_loc loc.loc_start |
| 55 | + in |
| 56 | + let found_if_case_contains cases = |
| 57 | + List.iter cases ~f:(fun (case : _ Typedtree.case) -> |
| 58 | + found_if_expr_contains case.c_rhs) |
| 59 | + in |
| 60 | + let expr_iter (iter : I.iterator) (expr : Typedtree.expression) = |
| 61 | + if range_contained_by_loc range expr.exp_loc then ( |
| 62 | + I.default_iterator.expr iter expr; |
| 63 | + match expr.exp_desc with |
| 64 | + | Texp_let (_, _, body) |
| 65 | + | Texp_while (_, body) |
| 66 | + | Texp_for (_, _, _, _, _, body) |
| 67 | + | Texp_letmodule (_, _, _, _, body) |
| 68 | + | Texp_letexception (_, body) |
| 69 | + | Texp_open (_, body) -> found_if_expr_contains body |
| 70 | + | Texp_letop { body; _ } -> found_if_case_contains [ body ] |
| 71 | + | Texp_function { cases; _ } -> found_if_case_contains cases |
| 72 | + | Texp_match (_, cases, _) -> found_if_case_contains cases |
| 73 | + | Texp_try (_, cases) -> found_if_case_contains cases |
| 74 | + | _ -> ()) |
| 75 | + in |
| 76 | + let structure_item_iter (iter : I.iterator) (item : Typedtree.structure_item) |
| 77 | + = |
| 78 | + if range_contained_by_loc range item.str_loc then ( |
| 79 | + I.default_iterator.structure_item iter item; |
| 80 | + match item.str_desc with |
| 81 | + | Tstr_value (_, bindings) -> |
| 82 | + List.iter bindings ~f:(fun (binding : Typedtree.value_binding) -> |
| 83 | + found_if_expr_contains binding.vb_expr) |
| 84 | + | _ -> ()) |
| 85 | + in |
| 86 | + let iterator = |
| 87 | + { I.default_iterator with |
| 88 | + expr = expr_iter |
| 89 | + ; structure_item = structure_item_iter |
| 90 | + } |
| 91 | + in |
| 92 | + try |
| 93 | + iterator.structure iterator typedtree; |
| 94 | + None |
| 95 | + with Found e -> Some e |
| 96 | + |
| 97 | +module LongidentSet = Set.Make (struct |
| 98 | + type t = Longident.t |
| 99 | + |
| 100 | + let compare = compare |
| 101 | +end) |
| 102 | + |
| 103 | +(** [free expr] returns the free variables in [expr]. *) |
| 104 | +let free (expr : Typedtree.expression) = |
| 105 | + let module I = Ocaml_typing.Tast_iterator in |
| 106 | + let idents = ref [] in |
| 107 | + let expr_iter (iter : I.iterator) (expr : Typedtree.expression) = |
| 108 | + match expr.exp_desc with |
| 109 | + | Texp_ident (path, { txt = ident; _ }, _) -> |
| 110 | + idents := (ident, path) :: !idents |
| 111 | + | _ -> |
| 112 | + I.default_iterator.expr iter expr; |
| 113 | + |
| 114 | + (* if a variable was bound but is no longer, it must be associated with a |
| 115 | + binder inside the expression *) |
| 116 | + idents := |
| 117 | + List.filter !idents ~f:(fun (ident, path) -> |
| 118 | + match Env.find_value_by_name ident expr.exp_env with |
| 119 | + | path', _ -> Path.same path path' |
| 120 | + | exception Not_found -> false) |
| 121 | + in |
| 122 | + let iter = { I.default_iterator with expr = expr_iter } in |
| 123 | + iter.expr iter expr; |
| 124 | + !idents |
| 125 | + |
| 126 | +let must_pass expr env = |
| 127 | + List.filter (free expr) ~f:(fun (ident, path) -> |
| 128 | + match Env.find_value_by_name ident env with |
| 129 | + | path', _ -> |
| 130 | + (* new environment binds ident to a different path than the old one *) |
| 131 | + not (Path.same path path') |
| 132 | + | exception Not_found -> true) |
| 133 | + |> List.map ~f:fst |
| 134 | + |
| 135 | +let extract_local doc typedtree range = |
| 136 | + let open Option.O in |
| 137 | + let* to_extract = largest_enclosed_expression typedtree range in |
| 138 | + let* extract_range = Range.of_loc_opt to_extract.exp_loc in |
| 139 | + let* edit_pos = tightest_enclosing_binder_position typedtree range in |
| 140 | + let new_name = "var_name" in |
| 141 | + let* local_text = Document.substring doc extract_range in |
| 142 | + let newText = sprintf "let %s = %s in\n" new_name local_text in |
| 143 | + let insert_range = { Range.start = edit_pos; end_ = edit_pos } in |
| 144 | + Some |
| 145 | + [ TextEdit.create ~newText ~range:insert_range |
| 146 | + ; TextEdit.create ~newText:new_name ~range:extract_range |
| 147 | + ] |
| 148 | + |
| 149 | +let extract_function doc typedtree range = |
| 150 | + let open Option.O in |
| 151 | + let* to_extract = largest_enclosed_expression typedtree range in |
| 152 | + let* extract_range = Range.of_loc_opt to_extract.exp_loc in |
| 153 | + let* parent_item = enclosing_structure_item typedtree range in |
| 154 | + let* edit_pos = Position.of_lexical_position parent_item.str_loc.loc_start in |
| 155 | + let new_name = "fun_name" in |
| 156 | + let* args_str = |
| 157 | + let free_vars = must_pass to_extract parent_item.str_env in |
| 158 | + let+ args = |
| 159 | + List.map free_vars ~f:(function |
| 160 | + | Longident.Lident id -> Some id |
| 161 | + | _ -> None) |
| 162 | + |> Option.List.all |
| 163 | + in |
| 164 | + let s = String.concat ~sep:" " args in |
| 165 | + if String.is_empty s then "()" else s |
| 166 | + in |
| 167 | + let* func_text = Document.substring doc extract_range in |
| 168 | + let new_function = sprintf "let %s %s = %s\n\n" new_name args_str func_text in |
| 169 | + let new_call = sprintf "%s %s" new_name args_str in |
| 170 | + let insert_range = { Range.start = edit_pos; end_ = edit_pos } in |
| 171 | + Some |
| 172 | + [ TextEdit.create ~newText:new_function ~range:insert_range |
| 173 | + ; TextEdit.create ~newText:new_call ~range:extract_range |
| 174 | + ] |
| 175 | + |
| 176 | +let run_extract_local doc (params : CodeActionParams.t) = |
| 177 | + let open Option.O in |
| 178 | + Document.Merlin.with_pipeline_exn |
| 179 | + (Document.merlin_exn doc) |
| 180 | + Mpipeline.typer_result |
| 181 | + |> Fiber.map ~f:(fun typer -> |
| 182 | + let* typedtree = |
| 183 | + match Mtyper.get_typedtree typer with |
| 184 | + | `Interface _ -> None |
| 185 | + | `Implementation x -> Some x |
| 186 | + in |
| 187 | + let+ edits = extract_local doc typedtree params.range in |
| 188 | + CodeAction.create |
| 189 | + ~title:"Extract local" |
| 190 | + ~kind:CodeActionKind.RefactorExtract |
| 191 | + ~edit:(Document.edit doc edits) |
| 192 | + ~isPreferred:false |
| 193 | + ()) |
| 194 | + |
| 195 | +let run_extract_function doc (params : CodeActionParams.t) = |
| 196 | + let open Option.O in |
| 197 | + Document.Merlin.with_pipeline_exn |
| 198 | + (Document.merlin_exn doc) |
| 199 | + Mpipeline.typer_result |
| 200 | + |> Fiber.map ~f:(fun typer -> |
| 201 | + let* typedtree = |
| 202 | + match Mtyper.get_typedtree typer with |
| 203 | + | `Interface _ -> None |
| 204 | + | `Implementation x -> Some x |
| 205 | + in |
| 206 | + let+ edits = extract_function doc typedtree params.range in |
| 207 | + CodeAction.create |
| 208 | + ~title:"Extract function" |
| 209 | + ~kind:CodeActionKind.RefactorExtract |
| 210 | + ~edit:(Document.edit doc edits) |
| 211 | + ~isPreferred:false |
| 212 | + ()) |
| 213 | + |
| 214 | +let local = { Code_action.kind = RefactorExtract; run = run_extract_local } |
| 215 | + |
| 216 | +let function_ = |
| 217 | + { Code_action.kind = RefactorExtract; run = run_extract_function } |
0 commit comments