2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
11 * FUTURE IMPROVEMENTS:
12 * - Order suggestions by how likely they are to be what the programmer
13 * wishes to do, not just what is valid
16 open FfpAutocompleteContextParser
17 open FfpAutocompleteContextParser.Container
18 open FfpAutocompleteContextParser.Predecessor
19 open FfpAutocompleteContextParser.ContextPredicates
22 (* Each keyword completion object has a list of keywords and a function that
23 takes a context and returns whether or not the list of keywords is valid
25 type keyword_completion
= {
26 keywords
: string list
;
27 is_valid_in_context
: context
-> bool;
30 let abstract_keyword = {
31 keywords
= ["abstract"];
32 is_valid_in_context
= begin fun context
->
34 is_top_level_statement_valid context
35 || (* Abstract method *)
36 is_class_body_declaration_valid context
||
37 is_trait_body_declaration_valid context
43 is_valid_in_context
= begin fun context
->
45 (context
.predecessor
= TopLevelDeclaration
||
46 context
.predecessor
= KeywordAbstract
)
48 is_top_level_statement_valid context
50 is_class_body_declaration_valid context
52 is_trait_body_declaration_valid context
53 || (* Final after other modifiers *)
54 context
.closest_parent_container
= ClassBody
&&
55 (context
.predecessor
= KeywordStatic
||
56 context
.predecessor
= VisibilityModifier
)
60 let implements_keyword = {
61 keywords
= ["implements"];
62 is_valid_in_context
= begin fun context
->
63 (* Class implements interface *)
64 (context
.closest_parent_container
= ClassHeader
||
65 context
.closest_parent_container
= ClassBody
) &&
66 (context
.predecessor
= ClassName
||
67 context
.predecessor
= ExtendsList
)
68 || (* "require implements" inside a trait *)
69 context
.closest_parent_container
= TraitBody
&&
70 context
.predecessor
= KeywordRequire
74 let extends_keyword = {
75 keywords
= ["extends"];
76 is_valid_in_context
= begin fun context
->
77 (context
.closest_parent_container
= InterfaceHeader
||
78 context
.closest_parent_container
= InterfaceBody
||
79 context
.closest_parent_container
= ClassHeader
||
80 context
.closest_parent_container
= ClassBody
) &&
81 context
.predecessor
= ClassName
82 || (* Inside trait/interface body *)
83 (context
.closest_parent_container
= TraitBody
||
84 context
.closest_parent_container
= InterfaceBody
) &&
85 context
.predecessor
= KeywordRequire
89 let visibility_modifiers = {
90 keywords
= ["public"; "protected"; "private"];
91 is_valid_in_context
= begin fun context
->
92 is_class_body_declaration_valid context
94 is_trait_body_declaration_valid context
96 context
.closest_parent_container
= ClassBody
&&
97 context
.predecessor
= KeywordFinal
101 let interface_visibility_modifiers = {
102 keywords
= ["public"];
103 is_valid_in_context
= is_interface_body_declaration_valid
106 let static_keyword = {
107 keywords
= ["static"];
108 is_valid_in_context
= begin fun context
->
109 is_class_body_declaration_valid context
||
110 is_interface_body_declaration_valid context
||
111 is_trait_body_declaration_valid context
113 (context
.closest_parent_container
= ClassBody
||
114 context
.closest_parent_container
= InterfaceBody
||
115 context
.closest_parent_container
= TraitBody
) &&
116 context
.predecessor
= VisibilityModifier
120 let async_keyword = {
121 keywords
= ["async"];
122 is_valid_in_context
= begin fun context
->
124 is_class_body_declaration_valid context
||
125 is_trait_body_declaration_valid context
126 || (* Async method after modifiers *)
127 (context
.closest_parent_container
= ClassBody
||
128 context
.closest_parent_container
= TraitBody
) &&
129 (context
.predecessor
= VisibilityModifier
||
130 context
.predecessor
= KeywordFinal
||
131 context
.predecessor
= KeywordStatic
)
132 || (* Async top level function *)
133 is_top_level_statement_valid context
134 || (* Async lambda *)
135 is_expression_valid context
139 let const_keyword = {
140 keywords
= ["const"];
141 is_valid_in_context
= begin fun context
->
142 is_class_body_declaration_valid context
||
143 is_interface_body_declaration_valid context
149 is_valid_in_context
= begin fun context
->
150 (* use <trait> inside body *)
151 is_class_body_declaration_valid context
152 || (* use <namespace> at top level *)
153 is_top_level_statement_valid context
154 (* TODO: "use" for closures *)
158 let function_keyword = {
159 keywords
= ["function"];
160 is_valid_in_context
= begin fun context
->
162 (* "function" is not valid without a visibility modifier, but we still suggest it here since a
163 user may wish to write the function before adding the modifier. *)
164 is_class_body_declaration_valid context
||
165 is_interface_body_declaration_valid context
||
166 is_trait_body_declaration_valid context
167 || (* Class method, after modifiers *)
168 (context
.closest_parent_container
= ClassBody
||
169 context
.closest_parent_container
= InterfaceBody
||
170 context
.closest_parent_container
= TraitBody
||
171 context
.closest_parent_container
= FunctionHeader
) &&
172 (context
.predecessor
= VisibilityModifier
||
173 context
.predecessor
= KeywordAsync
||
174 context
.predecessor
= KeywordStatic
||
175 context
.predecessor
= KeywordFinal
)
176 || (* Top level function *)
177 is_top_level_statement_valid context
178 || (* Top level async function *)
179 context
.closest_parent_container
= TopLevel
&&
180 context
.predecessor
= KeywordAsync
184 let class_keyword = {
185 keywords
= ["class"];
186 is_valid_in_context
= begin fun context
->
187 is_top_level_statement_valid context
189 context
.closest_parent_container
= ClassHeader
&&
190 (context
.predecessor
= KeywordAbstract
||
191 context
.predecessor
= KeywordFinal
)
195 let interface_keyword = {
196 keywords
= ["interface"];
197 is_valid_in_context
= begin fun context
->
198 is_top_level_statement_valid context
202 let require_constraint_keyword = {
203 keywords
= ["require"];
204 is_valid_in_context
= begin fun context
->
205 (* Require inside trait body or interface body *)
206 (context
.closest_parent_container
= TraitBody
||
207 context
.closest_parent_container
= InterfaceBody
) &&
208 (context
.predecessor
= TokenLeftBrace
||
209 context
.predecessor
= ClassBodyDeclaration
)
214 let declaration_keywords = {
215 keywords
= ["enum"; "require"; "include"; "require_once"; "include_once";
216 "namespace"; "newtype"; "trait"; "type"];
217 is_valid_in_context
= begin fun context
->
218 is_top_level_statement_valid context
224 is_valid_in_context
= begin fun context
->
225 context
.closest_parent_container
= FunctionHeader
&&
226 (context
.predecessor
= TokenColon
||
227 context
.predecessor
= TokenLessThan
)
231 let noreturn_keyword = {
232 keywords
= ["noreturn"];
233 is_valid_in_context
= begin fun context
->
234 (context
.closest_parent_container
= ClassBody
||
235 context
.closest_parent_container
= FunctionHeader
) &&
236 context
.predecessor
= TokenColon
240 let primitive_types = {
241 keywords
= ["array"; "arraykey"; "bool"; "classname"; "darray"; "float"; "int"; "mixed"; "num";
242 "string"; "resource"; "varray"];
243 is_valid_in_context
= is_type_valid
246 let this_type_keyword = {
247 keywords
= ["this"; "?this"];
248 is_valid_in_context
= begin fun context
->
249 context
.closest_parent_container
= FunctionHeader
&&
250 context
.predecessor
= TokenColon
&&
251 context
.inside_class_body
255 let loop_body_keywords = {
256 keywords
= ["continue"; "break"];
257 is_valid_in_context
= begin fun context
->
258 context
.inside_loop_body
&&
259 is_at_beginning_of_new_statement context
263 let switch_body_keywords = {
264 keywords
= ["case"; "default"; "break"];
265 is_valid_in_context
= begin fun context
->
266 context
.inside_switch_body
&&
267 is_at_beginning_of_new_statement context
271 let async_func_body_keywords = {
272 keywords
= ["await"];
273 is_valid_in_context
= begin fun context
->
274 context
.inside_async_function
&&
275 (context
.closest_parent_container
= CompoundStatement
||
276 context
.closest_parent_container
= AssignmentExpression
)
281 * TODO: Figure out what exactly a postfix expression is and when one is valid
282 * or more importantly, invalid.
284 let postfix_expressions = {
285 keywords
= ["clone"; "new"];
286 is_valid_in_context
= begin fun context
->
287 is_expression_valid context
291 let general_statements = {
292 keywords
= ["if"; "do"; "while"; "for"; "foreach"; "try"; "return"; "throw";
293 "switch"; "yield"; "echo"];
294 is_valid_in_context
= begin fun context
->
295 is_at_beginning_of_new_statement context
299 let if_after_else = {
301 is_valid_in_context
= begin fun context
->
302 context
.predecessor
= KeywordElse
&&
303 (context
.closest_parent_container
= CompoundStatement
||
304 context
.closest_parent_container
= IfStatement
)
308 let if_trailing_keywords = {
309 keywords
= ["else"; "else if"];
310 is_valid_in_context
= begin fun context
->
311 context
.predecessor
= IfWithoutElse
&&
312 context
.closest_parent_container
= CompoundStatement
316 let try_trailing_keywords = {
317 keywords
= ["catch"; "finally"];
318 is_valid_in_context
= begin fun context
->
319 context
.predecessor
= TryWithoutFinally
&&
320 context
.closest_parent_container
= CompoundStatement
324 let primary_expressions = {
325 keywords
= ["tuple"; "shape"; "null"];
326 is_valid_in_context
= begin fun context
->
327 is_expression_valid context
331 let scope_resolution_qualifiers = {
332 keywords
= ["self"; "parent"; "static"];
333 is_valid_in_context
= begin fun context
->
334 is_expression_valid context
338 let keyword_matches: keyword_completion list
= [
341 async_func_body_keywords;
344 declaration_keywords;
350 if_trailing_keywords;
353 interface_visibility_modifiers;
359 require_constraint_keyword;
360 scope_resolution_qualifiers;
362 switch_body_keywords;
364 try_trailing_keywords;
366 visibility_modifiers;
370 let autocomplete_keyword (context
:context
) : string list
=
371 let check_keyword_match { keywords
; is_valid_in_context
} =
372 Option.some_if
(is_valid_in_context context
) keywords
375 |> List.filter_map ~f
:check_keyword_match