Skip to content

Commit 5c1517f

Browse files
mheiberfacebook-github-bot
authored andcommitted
Extract shape type to alias
Summary: Add a new refactoring for creating a type alias from a shape hint. This is distinct from the existing refactoring which creates a type alias from a shape *expression*. https://pxl.cl/2SC6H ## Choice made Despite the two refactors being different, I combined them in the same file because: - this reduces the number of visitors and thereby save traversal cost - this saves some boilerplate without (imo) too much readability impairment Reviewed By: nt591 Differential Revision: D47188912 fbshipit-source-id: 1f7d8a6c659c5178263bc9b43b725ba8fb52c57c
1 parent 77d0d73 commit 5c1517f

13 files changed

+228
-24
lines changed

hphp/hack/src/server/server_code_actions_services/extract_shape_type.ml

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
open Hh_prelude
22

3-
type candidate = {
4-
tast_env: Tast_env.t;
5-
(* the function or class containing the shape expression *)
6-
container_pos: Pos.t;
7-
shape_ty: Typing_defs.locl_ty;
8-
}
3+
type candidate =
4+
| Of_expr of {
5+
tast_env: Tast_env.t;
6+
(* the function or class containing the shape expression *)
7+
expr_container_pos: Pos.t;
8+
shape_ty: Typing_defs.locl_ty;
9+
}
10+
| Of_hint of {
11+
hint_container_pos: Pos.t;
12+
hint_pos: Pos.t;
13+
}
14+
15+
(** We use distinct titles so we can tell the refactors apart in analytics *)
16+
let title_of_candidate = function
17+
| Of_expr _ -> "Extract shape type"
18+
| Of_hint _ -> "Extract shape type to alias"
919

1020
type state =
1121
| Searching of (Pos.t * candidate) option
@@ -54,6 +64,13 @@ let find_candidate ~(selection : Pos.t) ~entry ctx : candidate option =
5464
) else
5565
Searching None
5666

67+
method! on_type_hint_ env hint_ =
68+
match Option.both hint_ !container_pos with
69+
| Some ((hint_pos, Aast_defs.Hshape _), hint_container_pos)
70+
when Pos.contains selection hint_pos ->
71+
Searching (Some (hint_pos, Of_hint { hint_container_pos; hint_pos }))
72+
| _ -> super#on_type_hint_ env hint_
73+
5774
method! on_fun_def env fd =
5875
let pos = Aast_defs.(fd.fd_fun.f_span) in
5976
if Pos.contains pos selection then (
@@ -67,9 +84,12 @@ let find_candidate ~(selection : Pos.t) ~entry ctx : candidate option =
6784
if Pos.contains selection expr_pos then
6885
let ty_ = Typing_defs_core.get_node ty in
6986
match (ty_, !container_pos) with
70-
| (Typing_defs_core.Tshape _, Some container_pos) ->
87+
| (Typing_defs_core.Tshape _, Some expr_container_pos) ->
7188
Searching
72-
(Some (expr_pos, { tast_env = env; container_pos; shape_ty = ty }))
89+
(Some
90+
( expr_pos,
91+
Of_expr { tast_env = env; expr_container_pos; shape_ty = ty }
92+
))
7393
| _ -> Selected_non_shape_type expr_pos
7494
else
7595
super#on_expr env expr
@@ -81,24 +101,52 @@ let find_candidate ~(selection : Pos.t) ~entry ctx : candidate option =
81101
| Selected_non_shape_type _ ->
82102
None
83103

84-
let edit_of_candidate ~path { shape_ty; container_pos; tast_env } :
85-
Lsp.WorkspaceEdit.t =
86-
let edit =
87-
let pos = Pos.shrink_to_start container_pos in
88-
let range =
89-
Lsp_helpers.hack_pos_to_lsp_range ~equal:Relative_path.equal pos
90-
in
91-
let tenv = Tast_env.tast_env_as_typing_env tast_env in
92-
let ty_text = Typing_print.full_strip_ns tenv shape_ty in
93-
let text = Printf.sprintf "type T${0:placeholder_} = %s;\n\n" ty_text in
94-
Lsp.TextEdit.{ range; newText = text }
104+
let snippet_for_decl_of : string -> string =
105+
Printf.sprintf "type T${0:placeholder_} = %s;\n\n"
106+
107+
let snippet_for_use = "T${0:placeholder_}"
108+
109+
let range_of_container_pos container_pos : Lsp.range =
110+
let pos = Pos.shrink_to_start container_pos in
111+
Lsp_helpers.hack_pos_to_lsp_range ~equal:Relative_path.equal pos
112+
113+
let edit_of_candidate source_text ~path candidate : Lsp.WorkspaceEdit.t =
114+
let sub_of_pos = Full_fidelity_source_text.sub_of_pos source_text in
115+
let edits =
116+
match candidate with
117+
| Of_expr { shape_ty; expr_container_pos; tast_env } ->
118+
let range = range_of_container_pos expr_container_pos in
119+
let text =
120+
let ty_text =
121+
let tenv = Tast_env.tast_env_as_typing_env tast_env in
122+
Typing_print.full_strip_ns tenv shape_ty
123+
in
124+
snippet_for_decl_of ty_text
125+
in
126+
[Lsp.TextEdit.{ range; newText = text }]
127+
| Of_hint { hint_container_pos; hint_pos } ->
128+
let decl_edit =
129+
let range = range_of_container_pos hint_container_pos in
130+
let text =
131+
let ty_text = sub_of_pos hint_pos in
132+
snippet_for_decl_of ty_text
133+
in
134+
Lsp.TextEdit.{ range; newText = text }
135+
in
136+
let use_edit =
137+
let range =
138+
Lsp_helpers.hack_pos_to_lsp_range ~equal:Relative_path.equal hint_pos
139+
in
140+
Lsp.TextEdit.{ range; newText = snippet_for_use }
141+
in
142+
[decl_edit; use_edit]
95143
in
96-
let changes = SMap.singleton (Relative_path.to_absolute path) [edit] in
144+
let changes = SMap.singleton (Relative_path.to_absolute path) edits in
97145
Lsp.WorkspaceEdit.{ changes }
98146

99-
let to_refactor ~path candidate =
100-
let edit = lazy (edit_of_candidate ~path candidate) in
101-
Code_action_types.Refactor.{ title = "Extract shape type"; edit }
147+
let to_refactor source_text ~path candidate =
148+
let edit = lazy (edit_of_candidate source_text ~path candidate) in
149+
Code_action_types.Refactor.{ title = title_of_candidate candidate; edit }
102150

103151
let find ~entry ~(range : Lsp.range) ctx =
104152
let source_text = Ast_provider.compute_source_text ~entry in
@@ -108,5 +156,5 @@ let find ~entry ~(range : Lsp.range) ctx =
108156
let path = entry.Provider_context.path in
109157
let selection = Lsp_helpers.lsp_range_to_pos ~line_to_offset path range in
110158
find_candidate ~selection ~entry ctx
111-
|> Option.map ~f:(to_refactor ~path)
159+
|> Option.map ~f:(to_refactor source_text ~path)
112160
|> Option.to_list

hphp/hack/test/ide_code_actions/extract_shape_type_to_alias/HH_FLAGS

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(rule
2+
(alias ide_code_actions_extract_shape_type_to_alias)
3+
(deps
4+
%{exe:../../../src/hh_single_type_check.exe}
5+
%{project_root}/hack/test/verify.py
6+
%{project_root}/hack/test/review.sh
7+
(glob_files %{project_root}/hack/test/ide_code_actions/extract_shape_type_to_alias/HH_FLAGS)
8+
(glob_files %{project_root}/hack/test/ide_code_actions/extract_shape_type_to_alias/*.php)
9+
(glob_files %{project_root}/hack/test/ide_code_actions/extract_shape_type_to_alias/*.exp))
10+
(action
11+
(run
12+
%{project_root}/hack/test/verify.py
13+
%{project_root}/hack/test/ide_code_actions/extract_shape_type_to_alias
14+
--program
15+
%{exe:../../../src/hh_single_type_check.exe}
16+
--flags
17+
--ide-code-actions "Extract shape type")))
18+
19+
(alias
20+
(name runtest)
21+
(deps
22+
(alias ide_code_actions_extract_shape_type_to_alias)))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?hh
2+
3+
function foo(/*range-start*/shape('a' => int)/*range-end*/ $_): void {}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Code actions available:
2+
------------------------------------------
3+
Extract shape type to alias (CodeActionKind: refactor) SELECTED
4+
5+
JSON for selected code action:
6+
------------------------------------------
7+
{
8+
"diagnostics":[],
9+
"edit":{
10+
"changes":{
11+
"FILE.php":[
12+
{
13+
"newText":"type T${0:placeholder_} = shape('a' => int);\n\n",
14+
"range":{"end":{"character":0,"line":2},"start":{"character":0,"line":2}}
15+
},
16+
{
17+
"newText":"T${0:placeholder_}",
18+
"range":{"end":{"character":45,"line":2},"start":{"character":28,"line":2}}
19+
}
20+
]
21+
}
22+
},
23+
"kind":"refactor",
24+
"title":"Extract shape type to alias"
25+
}
26+
27+
Applied edit for code action:
28+
------------------------------------------
29+
<?hh
30+
31+
type T${0:placeholder_} = shape('a' => int);
32+
33+
function foo(/*range-start*/T${0:placeholder_}/*range-end*/ $_): void {}
34+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?hh
2+
3+
class A {
4+
public function foo(/*range-start*/shape('a' => int, ...)/*range-end*/ $_): void {}
5+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Code actions available:
2+
------------------------------------------
3+
Extract shape type to alias (CodeActionKind: refactor) SELECTED
4+
5+
JSON for selected code action:
6+
------------------------------------------
7+
{
8+
"diagnostics":[],
9+
"edit":{
10+
"changes":{
11+
"FILE.php":[
12+
{
13+
"newText":"type T${0:placeholder_} = shape('a' => int, ...);\n\n",
14+
"range":{"end":{"character":0,"line":2},"start":{"character":0,"line":2}}
15+
},
16+
{
17+
"newText":"T${0:placeholder_}",
18+
"range":{"end":{"character":59,"line":3},"start":{"character":37,"line":3}}
19+
}
20+
]
21+
}
22+
},
23+
"kind":"refactor",
24+
"title":"Extract shape type to alias"
25+
}
26+
27+
Applied edit for code action:
28+
------------------------------------------
29+
<?hh
30+
31+
type T${0:placeholder_} = shape('a' => int, ...);
32+
33+
class A {
34+
public function foo(/*range-start*/T${0:placeholder_}/*range-end*/ $_): void {}
35+
}
36+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?hh
2+
3+
type TShape = shape('a' => int);
4+
5+
class A {
6+
// we don't provide the refactoring unless a shape hint literal is selected
7+
public function foo(/*range-start*/TShape/*range-end*/ $_): void {}
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No commands or actions found
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?hh
2+
3+
class A {
4+
public function foo(shape('a' => /*range-start*/shape('inner' => int)/*range-end*/) $_): void {}
5+
}

0 commit comments

Comments
 (0)