4 from __future__
import absolute_import
, division
, print_function
, unicode_literals
13 from typing
import Iterable
, List
, Mapping
, Tuple
16 from hh_paths
import hh_server
17 from lspcommand
import LspCommandProcessor
, Transcript
18 from lsptestspec
import LspTestSpec
, NoResponse
, line
19 from test_case
import TestCase
20 from utils
import Json
, JsonObject
, interpolate_variables
23 class InsertTextFormat(enum
.Enum
):
28 class LspTestDriver(common_tests
.CommonTestDriver
):
29 def write_load_config(
30 self
, use_serverless_ide
: bool = False, use_saved_state
: bool = False
32 # Will use the .hhconfig already in the repo directory
33 # As for hh.conf, we'll write it explicitly each test.
34 with
open(os
.path
.join(self
.repo_dir
, "hh.conf"), "w") as f
:
38 watchman_subscribe_v2 = true
39 interrupt_on_watchman = true
40 interrupt_on_client = true
42 load_state_natively_v4 = {use_saved_state}
43 use_mini_state = {use_saved_state}
44 require_mini_state = {use_saved_state}
45 lazy_decl = {use_saved_state}
46 lazy_parse = {use_saved_state}
47 lazy_init2 = {use_saved_state}
48 symbolindex_search_provider = SqliteIndex
49 allow_unstable_features = true
50 ide_serverless = {use_serverless_ide}
52 use_saved_state
=str(use_saved_state
).lower(),
53 use_serverless_ide
=str(use_serverless_ide
).lower(),
57 def write_naming_table_saved_state(self
) -> str:
58 naming_table_saved_state_path
= os
.path
.join(
59 self
.repo_dir
, "naming_table_saved_state.sqlite"
61 (stdout
, stderr
, retcode
) = self
.proc_call(
67 naming_table_saved_state_path
,
70 assert retcode
== 0, (
71 f
"Failed to save naming table saved state: {retcode}\n"
72 + f
"STDOUT:\n{stdout}\n"
73 + f
"STDERR:\n{stderr}\n"
75 return naming_table_saved_state_path
78 class TestLsp(TestCase
[LspTestDriver
]):
80 def get_test_driver(cls
) -> LspTestDriver
:
81 return LspTestDriver()
84 def get_template_repo(cls
) -> str:
85 return "hphp/hack/test/integration/data/lsp_exchanges/"
87 def repo_file(self
, file: str) -> str:
88 return os
.path
.join(self
.test_driver
.repo_dir
, file)
90 def read_repo_file(self
, file: str) -> str:
91 with
open(self
.repo_file(file), "r") as f
:
94 def repo_file_uri(self
, file: str) -> str:
95 return urllib
.parse
.urljoin("file://", self
.repo_file(file))
97 # pyre-fixme[11]: Annotation `Json` is not defined as a type.
98 def parse_test_data(self
, file: str, variables
: Mapping
[str, str]) -> Json
:
99 text
= self
.read_repo_file(file)
100 data
: Json
= json
.loads(text
)
101 data
= interpolate_variables(data
, variables
)
105 self
, test_name
: str, variables
: Mapping
[str, str]
106 ) -> Tuple
[Json
, Json
]:
107 test
= self
.parse_test_data(test_name
+ ".json", variables
)
108 expected
= self
.parse_test_data(test_name
+ ".expected", variables
)
109 return (test
, expected
)
111 def write_observed(self
, test_name
: str, observed_transcript
: Json
) -> None:
112 file = os
.path
.join(self
.test_driver
.template_repo
, test_name
+ ".observed.log")
114 list(self
.get_important_received_items(observed_transcript
)), indent
=2
116 with
open(file, "w") as f
:
119 # pyre-fixme[11]: Annotation `JsonObject` is not defined as a type.
120 def order_response(self
, response
: JsonObject
) -> str:
122 return str(response
["id"])
124 return json
.dumps(response
, indent
=2)
126 # sorts a list of responses using the 'id' parameter so they can be
127 # compared in sequence even if they came back from the server out of sequence.
128 # this can happen based on how json rpc is specified to work.
129 # if 'id' isn't present the response is a notification. we sort notifications
130 # by their entire text.
131 def sort_responses(self
, responses
: Iterable
[JsonObject
]) -> List
[JsonObject
]:
132 return sorted(responses
, key
=lambda response
: self
.order_response(response
))
134 # removes stack traces from error responses since these can be noisy
135 # as code changes and they contain execution environment specific details
136 # by ignoring these when comparing responses we might miss some minor issues
137 # but will still catch the core error being thrown or not.
138 def sanitize_exceptions(
139 self
, responses
: Iterable
[JsonObject
]
140 ) -> Iterable
[JsonObject
]:
141 sanitized
= copy
.deepcopy(responses
)
142 for response
in sanitized
:
143 if "error" in response
:
144 if "data" in response
["error"]:
145 if "stack" in response
["error"]["data"]:
146 del response
["error"]["data"]["stack"]
147 if "current_stack" in response
["error"]["data"]:
148 del response
["error"]["data"]["current_stack"]
149 if "server_finale_stack" in response
["error"]["data"]:
150 del response
["error"]["data"]["server_finale_stack"]
153 # dumps an LSP response into a standard json format that can be used for
154 # doing precise text comparison in a way that is human readable in the case
155 # of there being an error.
156 def serialize_responses(self
, responses
: Iterable
[Json
]) -> List
[str]:
157 return [json
.dumps(response
, indent
=2) for response
in responses
]
159 # generates received responses from an LSP communication transcript
160 # ignoring the non-deterministic ones "progress" and "actionRequired"
161 def get_important_received_items(self
, transcript
: Transcript
) -> Iterable
[Json
]:
162 for entry
in transcript
.values():
163 received
= entry
.received
or None
166 method
= received
.get("method") or ""
169 "window/actionRequired",
176 # gets a set of loaded responses ready for validation by sorting them
177 # by id and serializing them for precise text comparison
178 def prepare_responses(self
, responses
: Iterable
[JsonObject
]) -> List
[str]:
179 return self
.serialize_responses(
180 self
.sanitize_exceptions(self
.sort_responses(responses
))
188 wait_for_server
: bool,
189 use_serverless_ide
: bool,
192 assert not use_serverless_ide
, (
193 "Warning: both `wait_for_server` and `use_serverless_ide` "
194 + "were set to `True` for testing in "
195 + self
.run_lsp_test
.__name
__
197 + "While this is a possible test case, it hasn't been written yet, "
198 + "so it's more likely that this is a mistake "
199 + "and you're accidentally relying on hh_server to fulfill "
200 + "serverless IDE requests."
201 + "(If you're writing that test, "
202 + "then it's time to remove this assertion.)"
205 # wait until hh_server is ready before starting lsp
206 self
.test_driver
.run_check()
207 elif use_serverless_ide
:
208 self
.test_driver
.stop_hh_server()
210 with LspCommandProcessor
.create(self
.test_driver
.test_env
) as lsp
:
211 observed_transcript
= lsp
.communicate(test
)
213 self
.write_observed(test_name
, observed_transcript
)
215 expected_items
= self
.prepare_responses(expected
)
216 observed_items
= self
.prepare_responses(
217 list(self
.get_important_received_items(observed_transcript
))
220 if not use_serverless_ide
:
221 # If the server's busy, maybe the machine's just under too much
222 # pressure to give results in a timely fashion. Doing a retry would
223 # only defer the question of what to do in that case, so instead
225 self
.throw_on_skip(observed_transcript
)
227 # validation checks that the number of items matches and that
228 # the responses are exactly identical to what we expect
232 "Wrong count. Observed this:\n"
233 + json
.dumps(observed_transcript
, indent
=2, separators
=(",", ": ")),
235 for i
in range(len(expected_items
)):
236 self
.assertEqual(expected_items
[i
], observed_items
[i
])
238 def throw_on_skip(self
, transcript
: Transcript
) -> None:
239 failure_messages
= ["Server busy", "timed out"]
240 for entry
in transcript
.values():
241 received
= entry
.received
244 if received
.get("error"):
245 message
= received
["error"]["message"]
246 for failure_message
in failure_messages
:
247 if failure_message
in message
:
248 raise unittest
.SkipTest(message
)
250 def prepare_server_environment(self
) -> None:
252 self
.test_driver
.write_load_config(use_serverless_ide
=False)
253 self
.test_driver
.start_hh_server()
254 (output
, err
, _
) = self
.test_driver
.run_check()
255 if "Error: Ran out of retries" in err
:
256 raise unittest
.SkipTest("Hack server could not be launched")
257 self
.assertEqual(output
.strip(), "No errors!")
259 def prepare_serverless_ide_environment(self
) -> Mapping
[str, str]:
261 self
.test_driver
.write_load_config(
262 use_serverless_ide
=True, use_saved_state
=False
264 naming_table_saved_state_path
= (
265 self
.test_driver
.write_naming_table_saved_state()
267 return {"naming_table_saved_state_path": naming_table_saved_state_path
}
272 variables
: Mapping
[str, str],
273 wait_for_server
: bool = True,
274 use_serverless_ide
: bool = False,
276 test
, expected
= self
.load_test_data(test_name
, variables
)
281 wait_for_server
=wait_for_server
,
282 use_serverless_ide
=use_serverless_ide
,
288 variables
: Mapping
[str, str],
289 wait_for_server
: bool,
290 use_serverless_ide
: bool,
293 # wait until hh_server is ready before starting lsp
294 self
.test_driver
.run_check()
295 elif use_serverless_ide
:
296 self
.test_driver
.stop_hh_server()
298 with LspCommandProcessor
.create(
299 self
.test_driver
.test_env
300 ) as lsp_command_processor
:
301 (observed_transcript
, error_details
) = spec
.run(
302 lsp_command_processor
=lsp_command_processor
, variables
=variables
304 file = os
.path
.join(self
.test_driver
.template_repo
, spec
.name
+ ".sent.log")
308 for sent
, _received
in observed_transcript
.values()
313 with
open(file, "w") as f
:
316 file = os
.path
.join(self
.test_driver
.template_repo
, spec
.name
+ ".received.log")
320 for _sent
, received
in observed_transcript
.values()
321 if received
is not None
325 with
open(file, "w") as f
:
328 if not use_serverless_ide
:
329 # If the server's busy, maybe the machine's just under too much
330 # pressure to give results in a timely fashion. Doing a retry would
331 # only defer the question of what to do in that case, so instead
333 self
.throw_on_skip(observed_transcript
)
335 if error_details
is not None:
336 raise AssertionError(error_details
)
338 def setup_php_file(self
, test_php
: str) -> Mapping
[str, str]:
339 # We want the path to the builtins directory. This is best we can do.
340 (output
, err
, retcode
) = self
.test_driver
.run_check(
341 options
=["--identify-function", "2:21", "--json"],
342 stdin
="<?hh\nfunction f():void {PHP_EOL;}\n",
346 "Could not discover builtins directory -- "
347 + "got exit code 7 (either Out_of_time or Out_of_retries). "
348 + "The test machine is likely under too much load."
350 self
.assertEqual(retcode
, 0)
351 constants_path
= json
.loads(output
)[0]["definition_pos"]["filename"]
353 "hhi_path": re
.sub("/constants.hhi$", "", constants_path
),
354 "root_path": self
.test_driver
.repo_dir
,
355 "php_file_uri": self
.repo_file_uri(test_php
),
356 "php_file": self
.read_repo_file(test_php
),
359 def test_init_shutdown(self
) -> None:
360 self
.prepare_server_environment()
363 "initialize_shutdown", {"root_path": self
.test_driver
.repo_dir
}
366 def test_serverless_ide_completion(self
) -> None:
367 variables
= dict(self
.prepare_serverless_ide_environment())
368 variables
.update(self
.setup_php_file("completion.php"))
369 self
.test_driver
.stop_hh_server()
371 self
.initialize_spec(LspTestSpec("ide_completion"), use_serverless_ide
=True)
373 method
="textDocument/didOpen",
376 "uri": "${php_file_uri}",
377 "languageId": "hack",
379 "text": "${php_file}",
384 comment
="Add '$x = $point1['' to test autocomplete for shapes",
385 method
="textDocument/didChange",
387 "textDocument": {"uri": "${php_file_uri}"},
391 "start": {"line": 22, "character": 0},
392 "end": {"line": 22, "character": 0},
394 "text": "$x = $point1['",
401 comment
="autocomplete after user types a shape",
402 method
="textDocument/completion",
404 "textDocument": {"uri": "${php_file_uri}"},
405 "position": {"line": 22, "character": 14},
408 "isIncomplete": False,
414 "inlineDetail": "literal",
417 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
420 "filename": "${root_path}/completion.php",
429 "inlineDetail": "literal",
432 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
435 "filename": "${root_path}/completion.php",
442 powered_by
="serverless_ide",
445 comment
="Add automatically closed apostrophes when typing a shape key, the way visual studio code does it",
446 method
="textDocument/didChange",
448 "textDocument": {"uri": "${php_file_uri}"},
452 "start": {"line": 22, "character": 0},
453 "end": {"line": 22, "character": 14},
455 "text": "$x = $point1['']",
462 comment
="autocomplete after a shape, with VS Code automatically closed apostrophes",
463 method
="textDocument/completion",
465 "textDocument": {"uri": "${php_file_uri}"},
466 "position": {"line": 22, "character": 14},
469 "isIncomplete": False,
475 "inlineDetail": "literal",
478 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
481 "filename": "${root_path}/completion.php",
490 "inlineDetail": "literal",
493 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
496 "filename": "${root_path}/completion.php",
503 powered_by
="serverless_ide",
506 comment
="Add '$x = <'",
507 method
="textDocument/didChange",
509 "textDocument": {"uri": "${php_file_uri}"},
513 "start": {"line": 3, "character": 0},
514 "end": {"line": 3, "character": 0},
523 comment
="autocomplete after '$x = <'",
524 method
="textDocument/completion",
526 "textDocument": {"uri": "${php_file_uri}"},
527 "position": {"line": 3, "character": 6},
530 "isIncomplete": False,
533 "label": "ab:cd:alpha",
536 "inlineDetail": "class",
537 "sortText": "ab:cd:alpha",
538 "insertText": "ab:cd:alpha",
539 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
540 "data": {"fullname": ":ab:cd:alpha"},
543 "label": "ab:cd:text",
546 "inlineDetail": "class",
547 "sortText": "ab:cd:text",
548 "insertText": "ab:cd:text",
549 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
550 "data": {"fullname": ":ab:cd:text"},
553 "label": "xhp:enum-attribute",
556 "inlineDetail": "class",
557 "sortText": "xhp:enum-attribute",
558 "insertText": "xhp:enum-attribute",
559 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
560 "data": {"fullname": ":xhp:enum-attribute"},
563 "label": "xhp:generic",
566 "inlineDetail": "class",
567 "sortText": "xhp:generic",
568 "insertText": "xhp:generic",
569 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
570 "data": {"fullname": ":xhp:generic"},
574 powered_by
="serverless_ide",
577 comment
="Add '$x = <a'",
578 method
="textDocument/didChange",
580 "textDocument": {"uri": "${php_file_uri}"},
584 "start": {"line": 3, "character": 0},
585 "end": {"line": 3, "character": 6},
594 comment
="autocomplete after '$x = <a'",
595 method
="textDocument/completion",
597 "textDocument": {"uri": "${php_file_uri}"},
598 "position": {"line": 3, "character": 7},
601 "isIncomplete": False,
604 "label": "ab:cd:alpha",
607 "inlineDetail": "class",
608 "sortText": "ab:cd:alpha",
609 "insertText": "ab:cd:alpha",
610 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
611 "data": {"fullname": ":ab:cd:alpha"},
614 "label": "ab:cd:text",
617 "inlineDetail": "class",
618 "sortText": "ab:cd:text",
619 "insertText": "ab:cd:text",
620 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
621 "data": {"fullname": ":ab:cd:text"},
625 powered_by
="serverless_ide",
628 comment
="Add '$x = <ab:'",
629 method
="textDocument/didChange",
631 "textDocument": {"uri": "${php_file_uri}"},
635 "start": {"line": 3, "character": 0},
636 "end": {"line": 3, "character": 7},
645 comment
="autocomplete after '$x = <ab:'",
646 method
="textDocument/completion",
648 "textDocument": {"uri": "${php_file_uri}"},
649 "position": {"line": 3, "character": 9},
652 "isIncomplete": False,
655 "label": "ab:cd:alpha",
658 "inlineDetail": "class",
659 "sortText": "ab:cd:alpha",
660 "insertText": "ab:cd:alpha",
661 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
662 "data": {"fullname": ":ab:cd:alpha"},
665 "label": "ab:cd:text",
668 "inlineDetail": "class",
669 "sortText": "ab:cd:text",
670 "insertText": "ab:cd:text",
671 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
672 "data": {"fullname": ":ab:cd:text"},
676 powered_by
="serverless_ide",
679 comment
="Add '$x = <ab:cd:text '",
680 method
="textDocument/didChange",
682 "textDocument": {"uri": "${php_file_uri}"},
686 "start": {"line": 3, "character": 0},
687 "end": {"line": 3, "character": 9},
689 "text": "$x = <ab:cd:text ",
696 comment
="autocomplete after '$x = <ab:cd:text '",
697 method
="textDocument/completion",
699 "textDocument": {"uri": "${php_file_uri}"},
700 "position": {"line": 3, "character": 17},
703 "isIncomplete": False,
709 "inlineDetail": "?int",
711 "insertText": "width",
712 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
714 "fullname": ":width",
715 "filename": "${root_path}/xhp_class_definitions.php",
718 "base_class": "\\:ab:cd:text",
725 "inlineDetail": "?string",
727 "insertText": "color",
728 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
730 "fullname": ":color",
731 "filename": "${root_path}/xhp_class_definitions.php",
734 "base_class": "\\:ab:cd:text",
739 powered_by
="serverless_ide",
742 comment
="Add '$x = <ab:cd:text w'",
743 method
="textDocument/didChange",
745 "textDocument": {"uri": "${php_file_uri}"},
749 "start": {"line": 3, "character": 0},
750 "end": {"line": 3, "character": 17},
752 "text": "$x = <ab:cd:text w",
759 comment
="autocomplete after '$x = <ab:cd:text w'",
760 method
="textDocument/completion",
762 "textDocument": {"uri": "${php_file_uri}"},
763 "position": {"line": 3, "character": 18},
766 "isIncomplete": False,
772 "inlineDetail": "?int",
774 "insertText": "width",
775 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
777 "fullname": ":width",
778 "filename": "${root_path}/xhp_class_definitions.php",
781 "base_class": "\\:ab:cd:text",
788 "inlineDetail": "?string",
790 "insertText": "color",
791 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
793 "fullname": ":color",
794 "filename": "${root_path}/xhp_class_definitions.php",
797 "base_class": "\\:ab:cd:text",
802 powered_by
="serverless_ide",
805 comment
="Add '$x = new :'",
806 method
="textDocument/didChange",
808 "textDocument": {"uri": "${php_file_uri}"},
812 "start": {"line": 3, "character": 0},
813 "end": {"line": 3, "character": 18},
815 "text": "$x = new :",
822 comment
="autocomplete after '$x = new :'",
823 method
="textDocument/completion",
825 "textDocument": {"uri": "${php_file_uri}"},
826 "position": {"line": 3, "character": 10},
829 "isIncomplete": False,
832 "label": ":ab:cd:alpha",
835 "inlineDetail": "class",
836 "sortText": ":ab:cd:alpha",
837 "insertText": ":ab:cd:alpha",
838 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
839 "data": {"fullname": ":ab:cd:alpha"},
842 "label": ":ab:cd:text",
845 "inlineDetail": "class",
846 "sortText": ":ab:cd:text",
847 "insertText": ":ab:cd:text",
848 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
849 "data": {"fullname": ":ab:cd:text"},
852 "label": ":xhp:enum-attribute",
855 "inlineDetail": "class",
856 "sortText": ":xhp:enum-attribute",
857 "insertText": ":xhp:enum-attribute",
858 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
859 "data": {"fullname": ":xhp:enum-attribute"},
862 "label": ":xhp:generic",
865 "inlineDetail": "class",
866 "sortText": ":xhp:generic",
867 "insertText": ":xhp:generic",
868 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
869 "data": {"fullname": ":xhp:generic"},
873 powered_by
="serverless_ide",
876 comment
="Add '$x = new :a'",
877 method
="textDocument/didChange",
879 "textDocument": {"uri": "${php_file_uri}"},
883 "start": {"line": 3, "character": 0},
884 "end": {"line": 3, "character": 10},
886 "text": "$x = new :a",
893 comment
="autocomplete after '$x = new :a'",
894 method
="textDocument/completion",
896 "textDocument": {"uri": "${php_file_uri}"},
897 "position": {"line": 3, "character": 11},
900 "isIncomplete": False,
903 "label": ":ab:cd:alpha",
906 "inlineDetail": "class",
907 "sortText": ":ab:cd:alpha",
908 "insertText": ":ab:cd:alpha",
909 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
910 "data": {"fullname": ":ab:cd:alpha"},
913 "label": ":ab:cd:text",
916 "inlineDetail": "class",
917 "sortText": ":ab:cd:text",
918 "insertText": ":ab:cd:text",
919 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
920 "data": {"fullname": ":ab:cd:text"},
924 powered_by
="serverless_ide",
926 # Note that this request should match the result in the previous example
929 comment
="autocomplete resolving after '$x = new :a'",
930 method
="completionItem/resolve",
932 "label": ":ab:cd:alpha",
935 "inlineDetail": "class",
936 "itemType": ":ab:cd:alpha",
937 "insertText": ":ab:cd:alpha",
938 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
939 "data": {"fullname": ":ab:cd:alpha"},
942 "label": ":ab:cd:alpha",
945 "inlineDetail": "class",
946 "itemType": ":ab:cd:alpha",
949 "value": ":ab:cd:alpha docblock",
951 "insertText": ":ab:cd:alpha",
952 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
953 "data": {"fullname": ":ab:cd:alpha"},
955 powered_by
="serverless_ide",
957 # Try the same thing again, but this time without "new", instead using "<xhp" style
959 comment
="Add '$x = <a'",
960 method
="textDocument/didChange",
962 "textDocument": {"uri": "${php_file_uri}"},
966 "start": {"line": 3, "character": 0},
967 "end": {"line": 3, "character": 11},
976 comment
="autocomplete after '$x = <a'",
977 method
="textDocument/completion",
979 "textDocument": {"uri": "${php_file_uri}"},
980 "position": {"line": 3, "character": 7},
983 "isIncomplete": False,
986 "label": "ab:cd:alpha",
989 "inlineDetail": "class",
990 "sortText": "ab:cd:alpha",
991 "insertText": "ab:cd:alpha",
992 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
993 "data": {"fullname": ":ab:cd:alpha"},
996 "label": "ab:cd:text",
999 "inlineDetail": "class",
1000 "sortText": "ab:cd:text",
1001 "insertText": "ab:cd:text",
1002 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1003 "data": {"fullname": ":ab:cd:text"},
1007 powered_by
="serverless_ide",
1011 comment
="autocomplete resolving after '$x = <a'",
1012 method
="completionItem/resolve",
1014 "label": "ab:cd:alpha",
1017 "inlineDetail": "class",
1018 "insertText": "ab:cd:alpha",
1019 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1020 "data": {"fullname": ":ab:cd:alpha"},
1023 "label": "ab:cd:alpha",
1026 "inlineDetail": "class",
1029 "value": ":ab:cd:alpha docblock",
1031 "insertText": "ab:cd:alpha",
1032 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1033 "data": {"fullname": ":ab:cd:alpha"},
1035 powered_by
="serverless_ide",
1038 comment
="Add '$x = <ab:cd:text/>; $y = $x->'",
1039 method
="textDocument/didChange",
1041 "textDocument": {"uri": "${php_file_uri}"},
1045 "start": {"line": 3, "character": 0},
1046 "end": {"line": 3, "character": 7},
1048 "text": "$x = <ab:cd:text/>; $y = $x->",
1055 comment
="autocomplete after '$x = <ab:cd:text/>; $y = $x->'",
1056 method
="textDocument/completion",
1058 "textDocument": {"uri": "${php_file_uri}"},
1059 "position": {"line": 3, "character": 29},
1062 "isIncomplete": False,
1068 "inlineDetail": "?int",
1069 "sortText": ":width",
1070 "insertText": ":width",
1071 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1073 "fullname": ":width",
1074 "filename": "${root_path}/xhp_class_definitions.php",
1077 "base_class": "\\:ab:cd:text",
1083 "detail": "?string",
1084 "inlineDetail": "?string",
1085 "sortText": ":color",
1086 "insertText": ":color",
1087 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1089 "fullname": ":color",
1090 "filename": "${root_path}/xhp_class_definitions.php",
1093 "base_class": "\\:ab:cd:text",
1098 powered_by
="serverless_ide",
1101 comment
="Add '$x = <ab:cd:text/>; $y = $x->:'",
1102 method
="textDocument/didChange",
1104 "textDocument": {"uri": "${php_file_uri}"},
1108 "start": {"line": 3, "character": 0},
1109 "end": {"line": 3, "character": 29},
1111 "text": "$x = <ab:cd:text/>; $y = $x->:",
1118 comment
="autocomplete after '$x = <ab:cd:text/>; $y = $x->:'",
1119 method
="textDocument/completion",
1121 "textDocument": {"uri": "${php_file_uri}"},
1122 "position": {"line": 3, "character": 30},
1125 "isIncomplete": False,
1131 "inlineDetail": "?int",
1132 "sortText": ":width",
1133 "insertText": ":width",
1134 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1136 "fullname": ":width",
1137 "filename": "${root_path}/xhp_class_definitions.php",
1140 "base_class": "\\:ab:cd:text",
1146 "detail": "?string",
1147 "inlineDetail": "?string",
1148 "sortText": ":color",
1149 "insertText": ":color",
1150 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1152 "fullname": ":color",
1153 "filename": "${root_path}/xhp_class_definitions.php",
1156 "base_class": "\\:ab:cd:text",
1161 powered_by
="serverless_ide",
1164 comment
="Add 'test_fun'",
1165 method
="textDocument/didChange",
1167 "textDocument": {"uri": "${php_file_uri}"},
1171 "start": {"line": 3, "character": 0},
1172 "end": {"line": 3, "character": 30},
1181 comment
="autocomplete after 'test_fun'",
1182 method
="textDocument/completion",
1184 "textDocument": {"uri": "${php_file_uri}"},
1185 "position": {"line": 3, "character": 8},
1188 "isIncomplete": False,
1191 "label": "test_function",
1193 "detail": "function",
1194 "inlineDetail": "function",
1195 "sortText": "test_function",
1196 "insertText": "test_function",
1197 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1198 "data": {"fullname": "test_function"},
1202 powered_by
="serverless_ide",
1206 comment
="autocomplete resolving after 'test_fun'",
1207 method
="completionItem/resolve",
1209 "label": "test_function",
1211 "detail": "function(): void",
1212 "inlineDetail": "()",
1214 "insertText": "test_function",
1215 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1217 "filename": "${root_path}/completion.php",
1223 "label": "test_function",
1225 "detail": "function(): void",
1226 "inlineDetail": "()",
1230 "value": "test_function docblock.",
1232 "insertText": "test_function",
1233 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1235 "filename": "${root_path}/completion.php",
1240 powered_by
="serverless_ide",
1243 comment
="Add 'switch (Elsa::Alonso) { case Elsa:'",
1244 method
="textDocument/didChange",
1246 "textDocument": {"uri": "${php_file_uri}"},
1250 "start": {"line": 3, "character": 0},
1251 "end": {"line": 3, "character": 8},
1253 "text": "switch (Elsa::Alonso) { case Elsa:",
1260 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa:'",
1261 method
="textDocument/completion",
1263 "textDocument": {"uri": "${php_file_uri}"},
1264 "position": {"line": 3, "character": 34},
1266 result
={"isIncomplete": False, "items": []},
1267 powered_by
="serverless_ide",
1270 comment
="Add 'switch (Elsa::Alonso) { case Elsa::'",
1271 method
="textDocument/didChange",
1273 "textDocument": {"uri": "${php_file_uri}"},
1277 "start": {"line": 3, "character": 0},
1278 "end": {"line": 3, "character": 34},
1280 "text": "switch (Elsa::Alonso) { case Elsa::",
1287 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa::'",
1288 method
="textDocument/completion",
1290 "textDocument": {"uri": "${php_file_uri}"},
1291 "position": {"line": 3, "character": 35},
1294 "isIncomplete": False,
1299 "detail": "classname<this>",
1300 "inlineDetail": "classname<this>",
1301 "sortText": "class",
1302 "insertText": "class",
1303 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1305 "fullname": "class",
1306 "filename": "${root_path}/completion_extras.php",
1309 "base_class": "\\Elsa",
1316 "inlineDetail": "Elsa",
1318 "insertText": "Bard",
1319 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1322 "filename": "${root_path}/completion_extras.php",
1325 "base_class": "\\Elsa",
1332 "inlineDetail": "Elsa",
1333 "sortText": "Alonso",
1334 "insertText": "Alonso",
1335 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1337 "fullname": "Alonso",
1338 "filename": "${root_path}/completion_extras.php",
1341 "base_class": "\\Elsa",
1347 "detail": "function(mixed $value): bool",
1348 "inlineDetail": "(mixed $value)",
1350 "sortText": "isValid",
1351 "insertText": "isValid(${1:\\$value})",
1352 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1354 "fullname": "isValid",
1355 "filename": "${hhi_path}/BuiltinEnum.hhi",
1358 "base_class": "\\Elsa",
1362 "label": "getValues",
1364 "detail": "function(): dict<string, Elsa>",
1365 "inlineDetail": "()",
1366 "itemType": "dict<string, Elsa>",
1367 "sortText": "getValues",
1368 "insertText": "getValues()",
1369 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1371 "fullname": "getValues",
1372 "filename": "${hhi_path}/BuiltinEnum.hhi",
1375 "base_class": "\\Elsa",
1379 "label": "getNames",
1381 "detail": "function(): dict<Elsa, string>",
1382 "inlineDetail": "()",
1383 "itemType": "dict<Elsa, string>",
1384 "sortText": "getNames",
1385 "insertText": "getNames()",
1386 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1388 "fullname": "getNames",
1389 "filename": "${hhi_path}/BuiltinEnum.hhi",
1392 "base_class": "\\Elsa",
1398 "detail": "function(mixed $value): ?Elsa",
1399 "inlineDetail": "(mixed $value)",
1400 "itemType": "?Elsa",
1401 "sortText": "coerce",
1402 "insertText": "coerce(${1:\\$value})",
1403 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1405 "fullname": "coerce",
1406 "filename": "${hhi_path}/BuiltinEnum.hhi",
1409 "base_class": "\\Elsa",
1413 "label": "assertAll",
1415 "detail": "function(Traversable<mixed> $values): Container<Elsa>",
1416 "inlineDetail": "(Traversable<mixed> $values)",
1417 "itemType": "Container<Elsa>",
1418 "sortText": "assertAll",
1419 "insertText": "assertAll(${1:\\$values})",
1420 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1422 "fullname": "assertAll",
1423 "filename": "${hhi_path}/BuiltinEnum.hhi",
1426 "base_class": "\\Elsa",
1432 "detail": "function(mixed $value): Elsa",
1433 "inlineDetail": "(mixed $value)",
1435 "sortText": "assert",
1436 "insertText": "assert(${1:\\$value})",
1437 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1439 "fullname": "assert",
1440 "filename": "${hhi_path}/BuiltinEnum.hhi",
1443 "base_class": "\\Elsa",
1448 powered_by
="serverless_ide",
1451 comment
="Add 'switch (Elsa::Alonso) { case Elsa::Alonso:'",
1452 method
="textDocument/didChange",
1454 "textDocument": {"uri": "${php_file_uri}"},
1458 "start": {"line": 3, "character": 0},
1459 "end": {"line": 3, "character": 35},
1461 "text": "switch (Elsa::Alonso) { case Elsa::Alonso:",
1468 comment
="docblock resolve after 'switch (Elsa::Alonso) { case Elsa::'",
1469 method
="completionItem/resolve",
1473 "detail": "function(mixed $value): bool",
1474 "inlineDetail": "(mixed $value)",
1476 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1479 "start": {"line": 3, "character": 35},
1480 "end": {"line": 3, "character": 35},
1482 "newText": "isValid",
1485 "filename": "${hhi_path}/BuiltinEnum.hhi",
1493 "detail": "function(mixed $value): bool",
1494 "inlineDetail": "(mixed $value)",
1498 "value": "Returns whether or not the value is defined as a constant.",
1500 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1503 "start": {"line": 3, "character": 35},
1504 "end": {"line": 3, "character": 35},
1506 "newText": "isValid",
1509 "filename": "${hhi_path}/BuiltinEnum.hhi",
1514 powered_by
="serverless_ide",
1518 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa::Alonso:'",
1519 method
="textDocument/completion",
1521 "textDocument": {"uri": "${php_file_uri}"},
1522 "position": {"line": 3, "character": 42},
1524 result
={"isIncomplete": False, "items": []},
1525 powered_by
="serverless_ide",
1528 comment
="Add 'TestNS\\'",
1529 method
="textDocument/didChange",
1531 "textDocument": {"uri": "${php_file_uri}"},
1535 "start": {"line": 3, "character": 0},
1536 "end": {"line": 3, "character": 42},
1545 comment
="autocomplete after 'TestNS\\'",
1546 method
="textDocument/completion",
1548 "textDocument": {"uri": "${php_file_uri}"},
1549 "position": {"line": 3, "character": 7},
1552 "isIncomplete": False,
1555 "label": "test_func",
1557 "detail": "function",
1558 "inlineDetail": "function",
1559 "sortText": "test_func",
1560 "insertText": "test_func",
1561 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1562 "data": {"fullname": "TestNS\\test_func"},
1566 powered_by
="serverless_ide",
1569 comment
="Add '$cc = new CompletionClass(); $cc->interfa'",
1570 method
="textDocument/didChange",
1572 "textDocument": {"uri": "${php_file_uri}"},
1576 "start": {"line": 3, "character": 0},
1577 "end": {"line": 3, "character": 7},
1579 "text": "$cc = new CompletionClass(); $cc->interfa",
1586 comment
="autocomplete after '$cc = new CompletionClass(); $cc->interfa'",
1587 method
="textDocument/completion",
1589 "textDocument": {"uri": "${php_file_uri}"},
1590 "position": {"line": 3, "character": 41},
1593 "isIncomplete": False,
1596 "label": "interfaceDocBlockMethod",
1598 "detail": "function(): void",
1599 "inlineDetail": "()",
1601 "sortText": "interfaceDocBlockMethod",
1602 "insertText": "interfaceDocBlockMethod()",
1603 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1605 "fullname": "interfaceDocBlockMethod",
1606 "filename": "${root_path}/completion.php",
1609 "base_class": "\\CompletionClass",
1614 powered_by
="serverless_ide",
1618 comment
="autocomplete resolving after '$cc = new CompletionClass(); $cc->interfa'",
1619 method
="completionItem/resolve",
1621 "label": "interfaceDocBlockMethod",
1623 "detail": "function(): void",
1624 "inlineDetail": "()",
1626 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1629 "start": {"line": 3, "character": 34},
1630 "end": {"line": 3, "character": 41},
1632 "newText": "interfaceDocBlockMethod",
1635 "filename": "${root_path}/completion.php",
1641 "label": "interfaceDocBlockMethod",
1643 "detail": "function(): void",
1644 "inlineDetail": "()",
1646 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1649 "start": {"line": 3, "character": 34},
1650 "end": {"line": 3, "character": 41},
1652 "newText": "interfaceDocBlockMethod",
1655 "filename": "${root_path}/completion.php",
1660 powered_by
="serverless_ide",
1663 comment
="Add 'DeprecatedClass::'",
1664 method
="textDocument/didChange",
1666 "textDocument": {"uri": "${php_file_uri}"},
1670 "start": {"line": 3, "character": 0},
1671 "end": {"line": 3, "character": 41},
1673 "text": "DeprecatedClass::",
1680 comment
="autocomplete after 'DeprecatedClass::'",
1681 method
="textDocument/completion",
1683 "textDocument": {"uri": "${php_file_uri}"},
1684 "position": {"line": 3, "character": 17},
1687 "isIncomplete": False,
1692 "detail": "classname<this>",
1693 "inlineDetail": "classname<this>",
1694 "sortText": "class",
1695 "insertText": "class",
1696 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1698 "fullname": "class",
1699 "filename": "${root_path}/completion_extras.php",
1702 "base_class": "\\DeprecatedClass",
1706 "label": "test_do_not_use",
1708 "detail": "function(): void",
1709 "inlineDetail": "()",
1711 "sortText": "~test_do_not_use",
1712 "insertText": "test_do_not_use()",
1713 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1715 "fullname": "test_do_not_use",
1716 "filename": "${root_path}/completion_extras.php",
1719 "base_class": "\\DeprecatedClass",
1725 "detail": "function(): void",
1726 "inlineDetail": "()",
1728 "sortText": "getName",
1729 "insertText": "getName()",
1730 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1732 "fullname": "getName",
1733 "filename": "${root_path}/completion_extras.php",
1736 "base_class": "\\DeprecatedClass",
1740 "label": "getAttributes_DO_NOT_USE",
1742 "detail": "function(): void",
1743 "inlineDetail": "()",
1745 "sortText": "~getAttributes_DO_NOT_USE",
1746 "insertText": "getAttributes_DO_NOT_USE()",
1747 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1749 "fullname": "getAttributes_DO_NOT_USE",
1750 "filename": "${root_path}/completion_extras.php",
1753 "base_class": "\\DeprecatedClass",
1757 "label": "__getLoader",
1759 "detail": "function(): void",
1760 "inlineDetail": "()",
1762 "sortText": "~__getLoader",
1763 "insertText": "__getLoader()",
1764 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
1766 "fullname": "__getLoader",
1767 "filename": "${root_path}/completion_extras.php",
1770 "base_class": "\\DeprecatedClass",
1775 powered_by
="serverless_ide",
1778 comment
="Add 'call_lambda(3, $m'",
1779 method
="textDocument/didChange",
1781 "textDocument": {"uri": "${php_file_uri}"},
1785 "start": {"line": 30, "character": 0},
1786 "end": {"line": 30, "character": 0},
1788 "text": " call_lambda(3, $m",
1795 comment
="autocomplete results for 'call_lambda(3, $m'",
1796 method
="textDocument/completion",
1798 "textDocument": {"uri": "${php_file_uri}"},
1799 "position": {"line": 30, "character": 19},
1802 "isIncomplete": False,
1805 "label": "$mylambda",
1807 "detail": "local variable",
1808 "inlineDetail": "(num $n)",
1810 "sortText": "$mylambda",
1811 "insertText": "$mylambda",
1812 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1814 "fullname": "$mylambda",
1815 "filename": "${root_path}/completion.php",
1822 powered_by
="serverless_ide",
1826 comment
="resolve autocompletion for $mylambda'",
1827 method
="completionItem/resolve",
1829 "label": "$mylambda",
1831 "detail": "local variable",
1832 "inlineDetail": "(num $n)",
1834 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1837 "start": {"line": 30, "character": 17},
1838 "end": {"line": 30, "character": 19},
1840 "newText": "$mylambda",
1843 "filename": "${root_path}/completion.php",
1849 "label": "$mylambda",
1851 "detail": "local variable",
1852 "inlineDetail": "(num $n)",
1854 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
1857 "start": {"line": 30, "character": 17},
1858 "end": {"line": 30, "character": 19},
1860 "newText": "$mylambda",
1863 "filename": "${root_path}/completion.php",
1868 powered_by
="serverless_ide",
1871 comment
="Add 'call_via_label#'",
1872 method
="textDocument/didChange",
1874 "textDocument": {"uri": "${php_file_uri}"},
1878 "start": {"line": 35, "character": 0},
1879 "end": {"line": 35, "character": 0},
1881 "text": " call_via_label#",
1888 comment
="autocomplete results for 'call_via_label#'",
1889 method
="textDocument/completion",
1891 "textDocument": {"uri": "${php_file_uri}"},
1892 "position": {"line": 35, "character": 17},
1895 "isIncomplete": False,
1900 "detail": "HH\\MemberOf<MyEnumClass, MyEnumClassKind>",
1901 "inlineDetail": "HH\\MemberOf<MyEnumClass, MyEnumClassKind>",
1902 "sortText": "First",
1903 "insertText": "First",
1904 "insertTextFormat": 1,
1906 "fullname": "First",
1907 "filename": "${root_path}/definition.php",
1910 "base_class": "\\MyEnumClass",
1916 "detail": "HH\\MemberOf<MyEnumClass, MyEnumClassKind>",
1917 "inlineDetail": "HH\\MemberOf<MyEnumClass, MyEnumClassKind>",
1918 "sortText": "Second",
1919 "insertText": "Second",
1920 "insertTextFormat": 1,
1922 "fullname": "Second",
1923 "filename": "${root_path}/definition.php",
1926 "base_class": "\\MyEnumClass",
1931 powered_by
="serverless_ide",
1934 comment
="Add '<xhp:enum-attribute enum-attribute={}'",
1935 method
="textDocument/didChange",
1937 "textDocument": {"uri": "${php_file_uri}"},
1941 "start": {"line": 3, "character": 0},
1942 "end": {"line": 3, "character": 17},
1944 "text": "<xhp:enum-attribute enum-attribute={}",
1951 comment
="autocomplete after '<xhp:enum-attribute enum-attribute={'",
1952 method
="textDocument/completion",
1954 "textDocument": {"uri": "${php_file_uri}"},
1955 "position": {"line": 3, "character": 36},
1956 "context": {"triggerKind": 2, "triggerCharacter": "{"},
1959 "isIncomplete": False,
1962 "label": "MyEnum::TYPE_C",
1965 "inlineDetail": "enum",
1966 "sortText": "MyEnum::TYPE_C",
1967 "insertText": "MyEnum::TYPE_C",
1968 "insertTextFormat": 1,
1970 "fullname": "MyEnum::TYPE_C",
1971 "filename": "${root_path}/xhp_class_definitions.php",
1974 "base_class": "\\MyEnum",
1978 "label": "MyEnum::TYPE_A",
1981 "inlineDetail": "enum",
1982 "sortText": "MyEnum::TYPE_A",
1983 "insertTextFormat": 1,
1984 "insertText": "MyEnum::TYPE_A",
1986 "fullname": "MyEnum::TYPE_A",
1987 "filename": "${root_path}/xhp_class_definitions.php",
1990 "base_class": "\\MyEnum",
1994 "label": "MyEnum::TYPE_B",
1997 "inlineDetail": "enum",
1998 "sortText": "MyEnum::TYPE_B",
1999 "insertTextFormat": 1,
2000 "insertText": "MyEnum::TYPE_B",
2002 "fullname": "MyEnum::TYPE_B",
2003 "filename": "${root_path}/xhp_class_definitions.php",
2006 "base_class": "\\MyEnum",
2011 powered_by
="serverless_ide",
2014 comment
="Add '1 is strin'",
2015 method
="textDocument/didChange",
2017 "textDocument": {"uri": "${php_file_uri}"},
2021 "start": {"line": 3, "character": 0},
2022 "end": {"line": 3, "character": 37},
2024 "text": "1 is strin",
2031 comment
="autocomplete after '1 is strin'",
2032 method
="textDocument/completion",
2034 "textDocument": {"uri": "${php_file_uri}"},
2035 "position": {"line": 3, "character": 10},
2038 "isIncomplete": False,
2041 "data": {"fullname": "string"},
2042 "detail": "builtin",
2045 "value": "A sequence of zero or more characters. Strings are usually manipulated with functions from the `Str\\` namespace",
2047 "inlineDetail": "builtin",
2048 "insertText": "string",
2049 "insertTextFormat": 1,
2052 "sortText": "string",
2055 "data": {"fullname": "StringBuffer"},
2057 "inlineDetail": "class",
2058 "insertText": "StringBuffer",
2059 "insertTextFormat": 1,
2061 "label": "StringBuffer",
2062 "sortText": "StringBuffer",
2065 "data": {"fullname": "Stringish"},
2066 "detail": "interface",
2067 "inlineDetail": "interface",
2068 "insertText": "Stringish",
2069 "insertTextFormat": 1,
2071 "label": "Stringish",
2072 "sortText": "Stringish",
2075 "data": {"fullname": "StringishObject"},
2076 "detail": "interface",
2077 "inlineDetail": "interface",
2078 "insertText": "StringishObject",
2079 "insertTextFormat": 1,
2081 "label": "StringishObject",
2082 "sortText": "StringishObject",
2086 powered_by
="serverless_ide",
2090 comment
="autocomplete resolving after '1 is strin'",
2091 method
="completionItem/resolve",
2093 "data": {"fullname": "string"},
2094 "detail": "builtin",
2097 "value": "A sequence of zero or more characters. Strings are usually manipulated with functions from the `Str\\` namespace",
2099 "inlineDetail": "builtin",
2100 "insertText": "string",
2101 "insertTextFormat": 1,
2104 "sortText": "string",
2107 "data": {"fullname": "string"},
2108 "detail": "builtin",
2111 "value": "A sequence of zero or more characters. Strings are usually manipulated with functions from the `Str\\` namespace",
2113 "inlineDetail": "builtin",
2114 "insertText": "string",
2115 "insertTextFormat": 1,
2118 "sortText": "string",
2120 powered_by
="serverless_ide",
2122 .request(line
=line(), method
="shutdown", params
={}, result
=None)
2123 .notification(method
="exit", params
={})
2125 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
2127 def test_serverless_ide_completion_legacy(self
) -> None:
2128 variables
= dict(self
.prepare_serverless_ide_environment())
2129 variables
.update(self
.setup_php_file("completion.php"))
2130 self
.test_driver
.stop_hh_server()
2133 self
.initialize_spec(
2134 LspTestSpec("serverless_ide_completion_legacy"), use_serverless_ide
=True
2137 method
="textDocument/didOpen",
2140 "uri": "${php_file_uri}",
2141 "languageId": "hack",
2143 "text": "${php_file}",
2148 comment
="Add '$x = <'",
2149 method
="textDocument/didChange",
2151 "textDocument": {"uri": "${php_file_uri}"},
2155 "start": {"line": 3, "character": 0},
2156 "end": {"line": 3, "character": 0},
2165 comment
="autocomplete after '$x = <'",
2166 method
="textDocument/completion",
2168 "textDocument": {"uri": "${php_file_uri}"},
2169 "position": {"line": 3, "character": 6},
2172 "isIncomplete": False,
2175 "label": "ab:cd:alpha",
2178 "inlineDetail": "class",
2179 "sortText": "ab:cd:alpha",
2180 "insertText": "ab:cd:alpha",
2181 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2182 "data": {"fullname": ":ab:cd:alpha"},
2185 "label": "ab:cd:text",
2188 "inlineDetail": "class",
2189 "sortText": "ab:cd:text",
2190 "insertText": "ab:cd:text",
2191 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2192 "data": {"fullname": ":ab:cd:text"},
2195 "label": "xhp:enum-attribute",
2198 "inlineDetail": "class",
2199 "sortText": "xhp:enum-attribute",
2200 "insertText": "xhp:enum-attribute",
2201 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2202 "data": {"fullname": ":xhp:enum-attribute"},
2205 "label": "xhp:generic",
2208 "inlineDetail": "class",
2209 "sortText": "xhp:generic",
2210 "insertText": "xhp:generic",
2211 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2212 "data": {"fullname": ":xhp:generic"},
2216 powered_by
="serverless_ide",
2219 comment
="Add '$x = <a'",
2220 method
="textDocument/didChange",
2222 "textDocument": {"uri": "${php_file_uri}"},
2226 "start": {"line": 3, "character": 0},
2227 "end": {"line": 3, "character": 6},
2236 comment
="autocomplete after '$x = <a'",
2237 method
="textDocument/completion",
2239 "textDocument": {"uri": "${php_file_uri}"},
2240 "position": {"line": 3, "character": 7},
2243 "isIncomplete": False,
2246 "label": "ab:cd:alpha",
2249 "inlineDetail": "class",
2250 "sortText": "ab:cd:alpha",
2251 "insertText": "ab:cd:alpha",
2252 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2253 "data": {"fullname": ":ab:cd:alpha"},
2256 "label": "ab:cd:text",
2259 "inlineDetail": "class",
2260 "sortText": "ab:cd:text",
2261 "insertText": "ab:cd:text",
2262 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2263 "data": {"fullname": ":ab:cd:text"},
2267 powered_by
="serverless_ide",
2270 comment
="Add '$x = <ab:'",
2271 method
="textDocument/didChange",
2273 "textDocument": {"uri": "${php_file_uri}"},
2277 "start": {"line": 3, "character": 0},
2278 "end": {"line": 3, "character": 7},
2280 "text": "$x = <ab:",
2287 comment
="autocomplete after '$x = <ab:'.",
2288 method
="textDocument/completion",
2290 "textDocument": {"uri": "${php_file_uri}"},
2291 "position": {"line": 3, "character": 9},
2294 "isIncomplete": False,
2297 "label": "ab:cd:alpha",
2300 "inlineDetail": "class",
2301 "sortText": "ab:cd:alpha",
2302 "insertText": "ab:cd:alpha",
2303 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2304 "data": {"fullname": ":ab:cd:alpha"},
2307 "label": "ab:cd:text",
2310 "inlineDetail": "class",
2311 "sortText": "ab:cd:text",
2312 "insertText": "ab:cd:text",
2313 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2314 "data": {"fullname": ":ab:cd:text"},
2318 powered_by
="serverless_ide",
2321 comment
="Add '$x = <ab:cd:text '",
2322 method
="textDocument/didChange",
2324 "textDocument": {"uri": "${php_file_uri}"},
2328 "start": {"line": 3, "character": 0},
2329 "end": {"line": 3, "character": 9},
2331 "text": "$x = <ab:cd:text ",
2338 comment
="autocomplete after '$x = <ab:cd:text '",
2339 method
="textDocument/completion",
2341 "textDocument": {"uri": "${php_file_uri}"},
2342 "position": {"line": 3, "character": 17},
2345 "isIncomplete": False,
2351 "inlineDetail": "?int",
2352 "sortText": "width",
2353 "insertText": "width",
2354 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2356 "fullname": ":width",
2357 "filename": "${root_path}/xhp_class_definitions.php",
2360 "base_class": "\\:ab:cd:text",
2366 "detail": "?string",
2367 "inlineDetail": "?string",
2368 "sortText": "color",
2369 "insertText": "color",
2370 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2372 "fullname": ":color",
2373 "filename": "${root_path}/xhp_class_definitions.php",
2376 "base_class": "\\:ab:cd:text",
2381 powered_by
="serverless_ide",
2384 comment
="Add '$x = <ab:cd:text w'",
2385 method
="textDocument/didChange",
2387 "textDocument": {"uri": "${php_file_uri}"},
2391 "start": {"line": 3, "character": 0},
2392 "end": {"line": 3, "character": 17},
2394 "text": "$x = <ab:cd:text w",
2401 comment
="autocomplete after '$x = <ab:cd:text w'",
2402 method
="textDocument/completion",
2404 "textDocument": {"uri": "${php_file_uri}"},
2405 "position": {"line": 3, "character": 18},
2408 "isIncomplete": False,
2414 "inlineDetail": "?int",
2415 "sortText": "width",
2416 "insertText": "width",
2417 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2419 "fullname": ":width",
2420 "filename": "${root_path}/xhp_class_definitions.php",
2423 "base_class": "\\:ab:cd:text",
2429 "detail": "?string",
2430 "inlineDetail": "?string",
2431 "sortText": "color",
2432 "insertText": "color",
2433 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2435 "fullname": ":color",
2436 "filename": "${root_path}/xhp_class_definitions.php",
2439 "base_class": "\\:ab:cd:text",
2444 powered_by
="serverless_ide",
2447 comment
="Add '$x = new :''",
2448 method
="textDocument/didChange",
2450 "textDocument": {"uri": "${php_file_uri}"},
2454 "start": {"line": 3, "character": 0},
2455 "end": {"line": 3, "character": 18},
2457 "text": "$x = new :",
2464 comment
="autocomplete after '$x = new :'",
2465 method
="textDocument/completion",
2467 "textDocument": {"uri": "${php_file_uri}"},
2468 "position": {"line": 3, "character": 10},
2471 "isIncomplete": False,
2474 "label": ":ab:cd:alpha",
2477 "inlineDetail": "class",
2478 "sortText": ":ab:cd:alpha",
2479 "insertText": ":ab:cd:alpha",
2480 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2481 "data": {"fullname": ":ab:cd:alpha"},
2484 "label": ":ab:cd:text",
2487 "inlineDetail": "class",
2488 "sortText": ":ab:cd:text",
2489 "insertText": ":ab:cd:text",
2490 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2491 "data": {"fullname": ":ab:cd:text"},
2494 "label": ":xhp:enum-attribute",
2497 "inlineDetail": "class",
2498 "sortText": ":xhp:enum-attribute",
2499 "insertText": ":xhp:enum-attribute",
2500 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2501 "data": {"fullname": ":xhp:enum-attribute"},
2504 "label": ":xhp:generic",
2507 "inlineDetail": "class",
2508 "sortText": ":xhp:generic",
2509 "insertText": ":xhp:generic",
2510 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2511 "data": {"fullname": ":xhp:generic"},
2515 powered_by
="serverless_ide",
2518 comment
="Add '$x = new :a'",
2519 method
="textDocument/didChange",
2521 "textDocument": {"uri": "${php_file_uri}"},
2525 "start": {"line": 3, "character": 0},
2526 "end": {"line": 3, "character": 10},
2528 "text": "$x = new :a",
2535 comment
="autocomplete after '$x = new :a'",
2536 method
="textDocument/completion",
2538 "textDocument": {"uri": "${php_file_uri}"},
2539 "position": {"line": 3, "character": 11},
2542 "isIncomplete": False,
2545 "label": ":ab:cd:alpha",
2548 "inlineDetail": "class",
2549 "sortText": ":ab:cd:alpha",
2550 "insertText": ":ab:cd:alpha",
2551 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2552 "data": {"fullname": ":ab:cd:alpha"},
2555 "label": ":ab:cd:text",
2558 "inlineDetail": "class",
2559 "sortText": ":ab:cd:text",
2560 "insertText": ":ab:cd:text",
2561 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2562 "data": {"fullname": ":ab:cd:text"},
2566 powered_by
="serverless_ide",
2568 # Note that this request sent should match the result given in the previous example
2571 comment
="autocomplete resolving after '$x = new :a'",
2572 method
="completionItem/resolve",
2574 "label": ":ab:cd:alpha",
2577 "inlineDetail": "class",
2578 "itemType": ":ab:cd:alpha",
2579 "insertText": ":ab:cd:alpha",
2580 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2581 "data": {"fullname": ":ab:cd:alpha"},
2584 "label": ":ab:cd:alpha",
2587 "inlineDetail": "class",
2588 "itemType": ":ab:cd:alpha",
2591 "value": ":ab:cd:alpha docblock",
2593 "insertText": ":ab:cd:alpha",
2594 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2595 "data": {"fullname": ":ab:cd:alpha"},
2597 powered_by
="serverless_ide",
2600 comment
="Add '$x = <ab:cd:text/>; $y = $x->'",
2601 method
="textDocument/didChange",
2603 "textDocument": {"uri": "${php_file_uri}"},
2607 "start": {"line": 3, "character": 0},
2608 "end": {"line": 3, "character": 11},
2610 "text": "$x = <ab:cd:text/>; $y = $x->",
2617 comment
="autocomplete after '$x = <ab:cd:text/>; $y = $x->'",
2618 method
="textDocument/completion",
2620 "textDocument": {"uri": "${php_file_uri}"},
2621 "position": {"line": 3, "character": 29},
2624 "isIncomplete": False,
2630 "inlineDetail": "?int",
2631 "sortText": ":width",
2632 "insertText": ":width",
2633 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2635 "fullname": ":width",
2636 "filename": "${root_path}/xhp_class_definitions.php",
2639 "base_class": "\\:ab:cd:text",
2645 "detail": "?string",
2646 "inlineDetail": "?string",
2647 "sortText": ":color",
2648 "insertText": ":color",
2649 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2651 "fullname": ":color",
2652 "filename": "${root_path}/xhp_class_definitions.php",
2655 "base_class": "\\:ab:cd:text",
2660 powered_by
="serverless_ide",
2663 comment
="Add '$x = <ab:cd:text/>; $y = $x->:'",
2664 method
="textDocument/didChange",
2666 "textDocument": {"uri": "${php_file_uri}"},
2670 "start": {"line": 3, "character": 0},
2671 "end": {"line": 3, "character": 29},
2673 "text": "$x = <ab:cd:text/>; $y = $x->:",
2680 comment
="autocomplete after '$x = <ab:cd:text/>; $y = $x->:'",
2681 method
="textDocument/completion",
2683 "textDocument": {"uri": "${php_file_uri}"},
2684 "position": {"line": 3, "character": 30},
2687 "isIncomplete": False,
2693 "inlineDetail": "?int",
2694 "sortText": ":width",
2695 "insertText": ":width",
2696 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2698 "fullname": ":width",
2699 "filename": "${root_path}/xhp_class_definitions.php",
2702 "base_class": "\\:ab:cd:text",
2708 "detail": "?string",
2709 "inlineDetail": "?string",
2710 "sortText": ":color",
2711 "insertText": ":color",
2712 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2714 "fullname": ":color",
2715 "filename": "${root_path}/xhp_class_definitions.php",
2718 "base_class": "\\:ab:cd:text",
2723 powered_by
="serverless_ide",
2726 comment
="Add 'test_fun'",
2727 method
="textDocument/didChange",
2729 "textDocument": {"uri": "${php_file_uri}"},
2733 "start": {"line": 3, "character": 0},
2734 "end": {"line": 3, "character": 30},
2743 comment
="autocomplete after 'test_fun'",
2744 method
="textDocument/completion",
2746 "textDocument": {"uri": "${php_file_uri}"},
2747 "position": {"line": 3, "character": 8},
2750 "isIncomplete": False,
2753 "label": "test_function",
2755 "detail": "function",
2756 "inlineDetail": "function",
2757 "sortText": "test_function",
2758 "insertText": "test_function",
2759 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2760 "data": {"fullname": "test_function"},
2764 powered_by
="serverless_ide",
2768 comment
="autocomplete resolving after 'test_fun'",
2769 method
="completionItem/resolve",
2771 "label": "test_function",
2773 "detail": "function(): void",
2774 "inlineDetail": "()",
2776 "insertText": "test_function",
2777 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2779 "filename": "${root_path}/completion.php",
2785 "label": "test_function",
2787 "detail": "function(): void",
2788 "inlineDetail": "()",
2792 "value": "test_function docblock.",
2794 "insertText": "test_function",
2795 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2797 "filename": "${root_path}/completion.php",
2802 powered_by
="serverless_ide",
2805 comment
="Add 'switch (Elsa::Alonso) { case Elsa:'",
2806 method
="textDocument/didChange",
2808 "textDocument": {"uri": "${php_file_uri}"},
2812 "start": {"line": 3, "character": 0},
2813 "end": {"line": 3, "character": 8},
2815 "text": "switch (Elsa::Alonso) { case Elsa:",
2822 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa:'",
2823 method
="textDocument/completion",
2825 "textDocument": {"uri": "${php_file_uri}"},
2826 "position": {"line": 3, "character": 34},
2828 result
={"isIncomplete": False, "items": []},
2829 powered_by
="serverless_ide",
2832 comment
="Add 'switch (Elsa::Alonso) { case Elsa::'",
2833 method
="textDocument/didChange",
2835 "textDocument": {"uri": "${php_file_uri}"},
2839 "start": {"line": 3, "character": 0},
2840 "end": {"line": 3, "character": 34},
2842 "text": "switch (Elsa::Alonso) { case Elsa::",
2849 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa::'",
2850 method
="textDocument/completion",
2852 "textDocument": {"uri": "${php_file_uri}"},
2853 "position": {"line": 3, "character": 35},
2856 "isIncomplete": False,
2861 "detail": "classname<this>",
2862 "inlineDetail": "classname<this>",
2863 "sortText": "class",
2864 "insertText": "class",
2865 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2867 "fullname": "class",
2868 "filename": "${root_path}/completion_extras.php",
2871 "base_class": "\\Elsa",
2878 "inlineDetail": "Elsa",
2880 "insertText": "Bard",
2881 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2884 "filename": "${root_path}/completion_extras.php",
2887 "base_class": "\\Elsa",
2894 "inlineDetail": "Elsa",
2895 "sortText": "Alonso",
2896 "insertText": "Alonso",
2897 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2899 "fullname": "Alonso",
2900 "filename": "${root_path}/completion_extras.php",
2903 "base_class": "\\Elsa",
2909 "detail": "function(mixed $value): bool",
2910 "inlineDetail": "(mixed $value)",
2912 "sortText": "isValid",
2913 "insertText": "isValid(${1:\\$value})",
2914 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2916 "fullname": "isValid",
2917 "filename": "${hhi_path}/BuiltinEnum.hhi",
2920 "base_class": "\\Elsa",
2924 "label": "getValues",
2926 "detail": "function(): dict<string, Elsa>",
2927 "inlineDetail": "()",
2928 "itemType": "dict<string, Elsa>",
2929 "sortText": "getValues",
2930 "insertText": "getValues()",
2931 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2933 "fullname": "getValues",
2934 "filename": "${hhi_path}/BuiltinEnum.hhi",
2937 "base_class": "\\Elsa",
2941 "label": "getNames",
2943 "detail": "function(): dict<Elsa, string>",
2944 "inlineDetail": "()",
2945 "itemType": "dict<Elsa, string>",
2946 "sortText": "getNames",
2947 "insertText": "getNames()",
2948 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2950 "fullname": "getNames",
2951 "filename": "${hhi_path}/BuiltinEnum.hhi",
2954 "base_class": "\\Elsa",
2960 "detail": "function(mixed $value): ?Elsa",
2961 "inlineDetail": "(mixed $value)",
2962 "itemType": "?Elsa",
2963 "sortText": "coerce",
2964 "insertText": "coerce(${1:\\$value})",
2965 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2967 "fullname": "coerce",
2968 "filename": "${hhi_path}/BuiltinEnum.hhi",
2971 "base_class": "\\Elsa",
2975 "label": "assertAll",
2977 "detail": "function(Traversable<mixed> $values): Container<Elsa>",
2978 "inlineDetail": "(Traversable<mixed> $values)",
2979 "itemType": "Container<Elsa>",
2980 "sortText": "assertAll",
2981 "insertText": "assertAll(${1:\\$values})",
2982 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2984 "fullname": "assertAll",
2985 "filename": "${hhi_path}/BuiltinEnum.hhi",
2988 "base_class": "\\Elsa",
2994 "detail": "function(mixed $value): Elsa",
2995 "inlineDetail": "(mixed $value)",
2997 "sortText": "assert",
2998 "insertText": "assert(${1:\\$value})",
2999 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3001 "fullname": "assert",
3002 "filename": "${hhi_path}/BuiltinEnum.hhi",
3005 "base_class": "\\Elsa",
3010 powered_by
="serverless_ide",
3013 comment
="Add 'switch (Elsa::Alonso) { case Elsa::Alonso:'",
3014 method
="textDocument/didChange",
3016 "textDocument": {"uri": "${php_file_uri}"},
3020 "start": {"line": 3, "character": 0},
3021 "end": {"line": 3, "character": 35},
3023 "text": "switch (Elsa::Alonso) { case Elsa::Alonso:",
3030 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa::Alonso:'",
3031 method
="textDocument/completion",
3033 "textDocument": {"uri": "${php_file_uri}"},
3034 "position": {"line": 3, "character": 42},
3036 result
={"isIncomplete": False, "items": []},
3037 powered_by
="serverless_ide",
3040 comment
="Add 'DeprecatedClass::'",
3041 method
="textDocument/didChange",
3043 "textDocument": {"uri": "${php_file_uri}"},
3047 "start": {"line": 3, "character": 0},
3048 "end": {"line": 3, "character": 41},
3050 "text": "DeprecatedClass::",
3057 comment
="autocomplete after 'DeprecatedClass::'",
3058 method
="textDocument/completion",
3060 "textDocument": {"uri": "${php_file_uri}"},
3061 "position": {"line": 3, "character": 17},
3064 "isIncomplete": False,
3069 "detail": "classname<this>",
3070 "inlineDetail": "classname<this>",
3071 "sortText": "class",
3072 "insertText": "class",
3073 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
3075 "fullname": "class",
3076 "filename": "${root_path}/completion_extras.php",
3079 "base_class": "\\DeprecatedClass",
3083 "label": "test_do_not_use",
3085 "detail": "function(): void",
3086 "inlineDetail": "()",
3088 "sortText": "~test_do_not_use",
3089 "insertText": "test_do_not_use()",
3090 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3092 "fullname": "test_do_not_use",
3093 "filename": "${root_path}/completion_extras.php",
3096 "base_class": "\\DeprecatedClass",
3102 "detail": "function(): void",
3103 "inlineDetail": "()",
3105 "sortText": "getName",
3106 "insertText": "getName()",
3107 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3109 "fullname": "getName",
3110 "filename": "${root_path}/completion_extras.php",
3113 "base_class": "\\DeprecatedClass",
3117 "label": "getAttributes_DO_NOT_USE",
3119 "detail": "function(): void",
3120 "inlineDetail": "()",
3122 "sortText": "~getAttributes_DO_NOT_USE",
3123 "insertText": "getAttributes_DO_NOT_USE()",
3124 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3126 "fullname": "getAttributes_DO_NOT_USE",
3127 "filename": "${root_path}/completion_extras.php",
3130 "base_class": "\\DeprecatedClass",
3134 "label": "__getLoader",
3136 "detail": "function(): void",
3137 "inlineDetail": "()",
3139 "sortText": "~__getLoader",
3140 "insertText": "__getLoader()",
3141 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3143 "fullname": "__getLoader",
3144 "filename": "${root_path}/completion_extras.php",
3147 "base_class": "\\DeprecatedClass",
3152 powered_by
="serverless_ide",
3154 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3155 .notification(method
="exit", params
={})
3157 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3159 def test_serverless_ide_definition(self
) -> None:
3160 variables
= dict(self
.prepare_serverless_ide_environment())
3161 variables
.update(self
.setup_php_file("definition.php"))
3162 self
.test_driver
.stop_hh_server()
3165 self
.initialize_spec(
3166 LspTestSpec("serverless_ide_definition"), use_serverless_ide
=True
3169 method
="textDocument/didOpen",
3172 "uri": "${php_file_uri}",
3173 "languageId": "hack",
3175 "text": "${php_file}",
3181 comment
="call to `b_definition`",
3182 method
="textDocument/definition",
3184 "textDocument": {"uri": "${php_file_uri}"},
3185 "position": {"line": 3, "character": 10},
3189 "uri": "file://${root_path}/definition.php",
3191 "start": {"line": 6, "character": 9},
3192 "end": {"line": 6, "character": 21},
3194 "title": "b_definition",
3197 powered_by
="serverless_ide",
3201 comment
="call to `new BB(1)`",
3202 method
="textDocument/definition",
3204 "textDocument": {"uri": "${php_file_uri}"},
3205 "position": {"line": 29, "character": 13},
3209 "uri": "file://${root_path}/definition.php",
3211 "start": {"line": 11, "character": 18},
3212 "end": {"line": 11, "character": 29},
3214 "title": "BB::__construct",
3217 powered_by
="serverless_ide",
3221 comment
="call to `new CC(1)`",
3222 method
="textDocument/definition",
3224 "textDocument": {"uri": "${php_file_uri}"},
3225 "position": {"line": 30, "character": 13},
3229 "uri": "file://${root_path}/definition.php",
3231 "start": {"line": 14, "character": 6},
3232 "end": {"line": 14, "character": 8},
3237 "uri": "file://${root_path}/definition.php",
3239 "start": {"line": 11, "character": 18},
3240 "end": {"line": 11, "character": 29},
3242 "title": "BB::__construct",
3245 powered_by
="serverless_ide",
3249 comment
="call to `new DD(1)`",
3250 method
="textDocument/definition",
3252 "textDocument": {"uri": "${php_file_uri}"},
3253 "position": {"line": 31, "character": 13},
3257 "uri": "file://${root_path}/definition.php",
3259 "start": {"line": 17, "character": 6},
3260 "end": {"line": 17, "character": 8},
3265 "uri": "file://${root_path}/definition.php",
3267 "start": {"line": 11, "character": 18},
3268 "end": {"line": 11, "character": 29},
3270 "title": "BB::__construct",
3273 powered_by
="serverless_ide",
3277 comment
="call to `new EE(1)`",
3278 method
="textDocument/definition",
3280 "textDocument": {"uri": "${php_file_uri}"},
3281 "position": {"line": 32, "character": 13},
3285 "uri": "file://${root_path}/definition.php",
3287 "start": {"line": 21, "character": 18},
3288 "end": {"line": 21, "character": 29},
3290 "title": "EE::__construct",
3293 powered_by
="serverless_ide",
3297 comment
="call to `new FF(1)`",
3298 method
="textDocument/definition",
3300 "textDocument": {"uri": "${php_file_uri}"},
3301 "position": {"line": 33, "character": 13},
3305 "uri": "file://${root_path}/definition.php",
3307 "start": {"line": 26, "character": 6},
3308 "end": {"line": 26, "character": 8},
3313 powered_by
="serverless_ide",
3317 comment
="call to `new TakesString(HasString::MyString)`",
3318 method
="textDocument/definition",
3320 "textDocument": {"uri": "${php_file_uri}"},
3321 "position": {"line": 45, "character": 23},
3325 "uri": "file://${root_path}/definition.php",
3327 "start": {"line": 40, "character": 6},
3328 "end": {"line": 40, "character": 15},
3330 "title": "HasString",
3333 powered_by
="serverless_ide",
3336 comment
="make local, unsaved change to the file",
3337 method
="textDocument/didChange",
3339 "textDocument": {"uri": "${php_file_uri}", "version": 2},
3344 "start": {"line": 3, "character": 9},
3345 "end": {"line": 3, "character": 21},
3353 comment
="call to `test` instead of `b_definition`",
3354 method
="textDocument/definition",
3356 "textDocument": {"uri": "${php_file_uri}"},
3357 "position": {"line": 3, "character": 10},
3361 "uri": "file://${root_path}/definition.php",
3363 "start": {"line": 28, "character": 9},
3364 "end": {"line": 28, "character": 13},
3369 powered_by
="serverless_ide",
3371 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3372 .notification(method
="exit", params
={})
3374 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3376 def test_serverless_ide_overridden_definition(self
) -> None:
3377 variables
= dict(self
.prepare_serverless_ide_environment())
3378 variables
.update(self
.setup_php_file("override.php"))
3379 self
.test_driver
.stop_hh_server()
3382 self
.initialize_spec(
3383 LspTestSpec("serverless_ide_overridden_definition"),
3384 use_serverless_ide
=True,
3387 method
="textDocument/didOpen",
3390 "uri": "${php_file_uri}",
3391 "languageId": "hack",
3393 "text": "${php_file}",
3399 comment
="find overridden method from trait. It's arbitrary which one we pick. This test embodies current (alphabetical) implementation.",
3400 method
="textDocument/definition",
3402 "textDocument": {"uri": "${php_file_uri}"},
3403 "position": {"line": 13, "character": 5},
3407 "uri": "file://${root_path}/override.php",
3409 "start": {"line": 7, "character": 18},
3410 "end": {"line": 7, "character": 21},
3412 "title": "MyTrait::foo",
3415 powered_by
="serverless_ide",
3419 comment
="find overridden static method. It's arbitrary which one we pick. This test embodies current (alphabetical) implementation.",
3420 method
="textDocument/definition",
3422 "textDocument": {"uri": "${php_file_uri}"},
3423 "position": {"line": 26, "character": 5},
3427 "uri": "file://${root_path}/override.php",
3429 "start": {"line": 23, "character": 25},
3430 "end": {"line": 23, "character": 28},
3435 powered_by
="serverless_ide",
3439 comment
="find overridden interface method",
3440 method
="textDocument/definition",
3442 "textDocument": {"uri": "${php_file_uri}"},
3443 "position": {"line": 35, "character": 5},
3447 "uri": "file://${root_path}/override.php",
3449 "start": {"line": 32, "character": 18},
3450 "end": {"line": 32, "character": 22},
3452 "title": "I1::quux",
3455 powered_by
="serverless_ide",
3457 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3458 .notification(method
="exit", params
={})
3460 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3462 def test_serverless_ide_document_symbol(self
) -> None:
3463 variables
= dict(self
.prepare_serverless_ide_environment())
3464 variables
.update(self
.setup_php_file("definition.php"))
3465 self
.test_driver
.stop_hh_server()
3468 self
.initialize_spec(
3469 LspTestSpec("serverless_ide_document_symbol"), use_serverless_ide
=True
3472 method
="textDocument/didOpen",
3475 "uri": "${php_file_uri}",
3476 "languageId": "hack",
3478 "text": "${php_file}",
3484 comment
="documentSymbol call",
3485 method
="textDocument/documentSymbol",
3486 params
={"textDocument": {"uri": "${php_file_uri}"}},
3492 "uri": "file://${root_path}/definition.php",
3494 "start": {"line": 50, "character": 18},
3495 "end": {"line": 50, "character": 47},
3498 "containerName": "MyEnumClass",
3501 "name": "MyEnumClass",
3504 "uri": "file://${root_path}/definition.php",
3506 "start": {"line": 49, "character": 0},
3507 "end": {"line": 52, "character": 1},
3512 "name": "testClassMemberInsideConstructorInvocation",
3515 "uri": "file://${root_path}/definition.php",
3517 "start": {"line": 44, "character": 0},
3518 "end": {"line": 46, "character": 1},
3526 "uri": "file://${root_path}/definition.php",
3528 "start": {"line": 41, "character": 8},
3529 "end": {"line": 41, "character": 29},
3532 "containerName": "HasString",
3535 "name": "HasString",
3538 "uri": "file://${root_path}/definition.php",
3540 "start": {"line": 40, "character": 0},
3541 "end": {"line": 42, "character": 1},
3546 "name": "__construct",
3549 "uri": "file://${root_path}/definition.php",
3551 "start": {"line": 37, "character": 2},
3552 "end": {"line": 37, "character": 43},
3555 "containerName": "TakesString",
3558 "name": "TakesString",
3561 "uri": "file://${root_path}/definition.php",
3563 "start": {"line": 36, "character": 0},
3564 "end": {"line": 38, "character": 1},
3572 "uri": "file://${root_path}/definition.php",
3574 "start": {"line": 26, "character": 0},
3575 "end": {"line": 26, "character": 11},
3580 "name": "__construct",
3583 "uri": "file://${root_path}/definition.php",
3585 "start": {"line": 21, "character": 2},
3586 "end": {"line": 23, "character": 3},
3589 "containerName": "EE",
3595 "uri": "file://${root_path}/definition.php",
3597 "start": {"line": 20, "character": 0},
3598 "end": {"line": 24, "character": 1},
3606 "uri": "file://${root_path}/definition.php",
3608 "start": {"line": 14, "character": 0},
3609 "end": {"line": 15, "character": 1},
3614 "name": "__construct",
3617 "uri": "file://${root_path}/definition.php",
3619 "start": {"line": 11, "character": 2},
3620 "end": {"line": 11, "character": 40},
3623 "containerName": "BB",
3629 "uri": "file://${root_path}/definition.php",
3631 "start": {"line": 10, "character": 0},
3632 "end": {"line": 12, "character": 1},
3637 "name": "a_definition",
3640 "uri": "file://${root_path}/definition.php",
3642 "start": {"line": 2, "character": 0},
3643 "end": {"line": 4, "character": 1},
3648 "name": "b_definition",
3651 "uri": "file://${root_path}/definition.php",
3653 "start": {"line": 6, "character": 0},
3654 "end": {"line": 8, "character": 1},
3662 "uri": "file://${root_path}/definition.php",
3664 "start": {"line": 17, "character": 0},
3665 "end": {"line": 18, "character": 1},
3673 "uri": "file://${root_path}/definition.php",
3675 "start": {"line": 28, "character": 0},
3676 "end": {"line": 34, "character": 1},
3681 "name": "MyEnumClassKind",
3684 "uri": "file://${root_path}/definition.php",
3686 "start": {"line": 48, "character": 0},
3687 "end": {"line": 48, "character": 24},
3695 "uri": "${php_file_uri}",
3697 "start": {"line": 51, "character": 18},
3698 "end": {"line": 51, "character": 48},
3701 "containerName": "MyEnumClass",
3704 powered_by
="serverless_ide",
3706 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3707 .notification(method
="exit", params
={})
3709 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3711 def initialize_spec(
3714 use_serverless_ide
: bool,
3715 supports_status
: bool = False, # does the caller wish to see all status messages?
3716 supports_init
: bool = False, # do we wish to interact with init, rather than waiting for init ok?
3718 if use_serverless_ide
:
3719 initialization_options
= {
3720 "namingTableSavedStatePath": "${naming_table_saved_state_path}",
3721 "namingTableSavedStateTestDelay": 0.0,
3724 # A small delay, since otherwise init completes immediately
3725 # This isn't very racy. All we need is a tiny delay so that
3726 # other things which are in the queue get processed, rather
3727 # than continuing synchronously
3728 initialization_options
["namingTableSavedStateTestDelay"] = 0.5
3730 initialization_options
= {}
3732 window_capabilities
= {}
3734 window_capabilities
["status"] = {"dynamicRegistration": False}
3736 spec
= spec
.ignore_notifications(method
="telemetry/event").request(
3738 method
="initialize",
3740 "initializationOptions": initialization_options
,
3742 "rootPath": "${root_path}",
3744 "window": window_capabilities
,
3746 "completion": {"completionItem": {"snippetSupport": True}}
3752 "textDocumentSync": {
3756 "willSaveWaitUntil": True,
3757 "save": {"includeText": False},
3759 "hoverProvider": True,
3760 "completionProvider": {
3761 "resolveProvider": True,
3762 "triggerCharacters": [
3775 "signatureHelpProvider": {"triggerCharacters": ["(", ","]},
3776 "definitionProvider": True,
3777 "typeDefinitionProvider": True,
3778 "referencesProvider": True,
3779 "documentHighlightProvider": True,
3780 "documentSymbolProvider": True,
3781 "workspaceSymbolProvider": True,
3782 "codeActionProvider": True,
3783 "documentFormattingProvider": True,
3784 "documentRangeFormattingProvider": True,
3785 "documentOnTypeFormattingProvider": {
3786 "firstTriggerCharacter": ";",
3787 "moreTriggerCharacter": ["}"],
3789 "renameProvider": True,
3790 "implementationProvider": True,
3791 "typeCoverageProvider": True,
3792 "rageProvider": True,
3796 if use_serverless_ide
:
3797 spec
= spec
.wait_for_server_request(
3798 method
="client/registerCapability",
3802 "id": "did-change-watched-files",
3803 "method": "workspace/didChangeWatchedFiles",
3804 "registerOptions": {
3807 "globPattern": "**/*.{php,phpt,hack,hackpartial,hck,hh,hhi,xhp}",
3817 if not supports_status
:
3818 spec
= spec
.ignore_status_diagnostics(True)
3820 if use_serverless_ide
and not supports_init
:
3821 spec
= spec
.wait_for_notification(
3822 comment
="wait for sIDE to finish init",
3823 method
="telemetry/event",
3824 params
={"type": 4, "message": "[client-ide] Finished init: ok"},
3829 def test_serverless_ide_type_definition(self
) -> None:
3830 variables
= dict(self
.prepare_serverless_ide_environment())
3831 variables
.update(self
.setup_php_file("type_definition.php"))
3832 self
.test_driver
.stop_hh_server()
3835 self
.initialize_spec(
3836 LspTestSpec("serverless_ide_type_definition"), use_serverless_ide
=True
3839 method
="textDocument/didOpen",
3842 "uri": "${php_file_uri}",
3843 "languageId": "hack",
3845 "text": "${php_file}",
3851 comment
="Conditional Type Definition of HH or II",
3852 method
="textDocument/typeDefinition",
3854 "textDocument": {"uri": "${php_file_uri}"},
3855 "position": {"line": 32, "character": 2},
3859 "uri": "${php_file_uri}",
3861 "start": {"line": 2, "character": 6},
3862 "end": {"line": 2, "character": 8},
3867 "uri": "${php_file_uri}",
3869 "start": {"line": 12, "character": 6},
3870 "end": {"line": 12, "character": 8},
3875 powered_by
="serverless_ide",
3879 comment
="Standard Class Definition",
3880 method
="textDocument/typeDefinition",
3882 "textDocument": {"uri": "${php_file_uri}"},
3883 "position": {"line": 40, "character": 2},
3887 "uri": "${php_file_uri}",
3889 "start": {"line": 2, "character": 6},
3890 "end": {"line": 2, "character": 8},
3895 powered_by
="serverless_ide",
3899 comment
="Class Type Definition with Casting",
3900 method
="textDocument/typeDefinition",
3902 "textDocument": {"uri": "${php_file_uri}"},
3903 "position": {"line": 41, "character": 2},
3907 "uri": "${php_file_uri}",
3909 "start": {"line": 2, "character": 6},
3910 "end": {"line": 2, "character": 8},
3915 powered_by
="serverless_ide",
3919 comment
="Primitive Type Definition",
3920 method
="textDocument/typeDefinition",
3922 "textDocument": {"uri": "${php_file_uri}"},
3923 "position": {"line": 42, "character": 2},
3926 powered_by
="serverless_ide",
3930 comment
="Function Return Type Definition",
3931 method
="textDocument/typeDefinition",
3933 "textDocument": {"uri": "${php_file_uri}"},
3934 "position": {"line": 43, "character": 2},
3938 "uri": "${php_file_uri}",
3940 "start": {"line": 12, "character": 6},
3941 "end": {"line": 12, "character": 8},
3946 powered_by
="serverless_ide",
3950 comment
="Function definition with primitive return type",
3951 method
="textDocument/typeDefinition",
3953 "textDocument": {"uri": "${php_file_uri}"},
3954 "position": {"line": 44, "character": 2},
3958 "uri": "${php_file_uri}",
3960 "start": {"line": 22, "character": 9},
3961 "end": {"line": 22, "character": 29},
3963 "title": "(function(): int)",
3966 powered_by
="serverless_ide",
3968 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3969 .notification(method
="exit", params
={})
3971 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3973 def test_serverless_ide_hover(self
) -> None:
3974 variables
= dict(self
.prepare_serverless_ide_environment())
3975 variables
.update(self
.setup_php_file("hover.php"))
3976 self
.test_driver
.stop_hh_server()
3979 self
.initialize_spec(
3980 LspTestSpec("serverless_ide_hover"), use_serverless_ide
=True
3983 method
="textDocument/didOpen",
3986 "uri": "${php_file_uri}",
3987 "languageId": "hack",
3989 "text": "${php_file}",
3995 comment
="hover over function invocation",
3996 method
="textDocument/hover",
3998 "textDocument": {"uri": "${php_file_uri}"},
3999 "position": {"line": 3, "character": 16},
4003 {"language": "hack", "value": "int"},
4004 "A comment describing b_hover.",
4007 "start": {"line": 3, "character": 9},
4008 "end": {"line": 3, "character": 16},
4011 powered_by
="serverless_ide",
4015 comment
="hover over string literal outside call",
4016 method
="textDocument/hover",
4018 "textDocument": {"uri": "${php_file_uri}"},
4019 "position": {"line": 25, "character": 12}, # 9 - 16
4021 result
={"contents": [{"language": "hack", "value": "string"}]},
4022 powered_by
="serverless_ide",
4026 comment
="hover over string literal inside call",
4027 method
="textDocument/hover",
4029 "textDocument": {"uri": "${php_file_uri}"},
4030 "position": {"line": 26, "character": 20}, # 16 - 29
4032 result
={"contents": [{"language": "hack", "value": "string"}]},
4033 powered_by
="serverless_ide",
4037 comment
="hover over int literal inside call",
4038 method
="textDocument/hover",
4040 "textDocument": {"uri": "${php_file_uri}"},
4041 "position": {"line": 26, "character": 32}, # 31 - 33
4043 result
={"contents": [{"language": "hack", "value": "int"}]},
4044 powered_by
="serverless_ide",
4048 comment
="hover over constant reference",
4049 method
="textDocument/hover",
4051 "textDocument": {"uri": "${php_file_uri}"},
4052 "position": {"line": 15, "character": 19},
4056 {"language": "hack", "value": "THE_ANSWER"},
4057 "A comment describing THE_ANSWER",
4058 "int THE_ANSWER = 42",
4061 "start": {"line": 15, "character": 9},
4062 "end": {"line": 15, "character": 19},
4065 powered_by
="serverless_ide",
4069 comment
="hover over whitespace",
4070 method
="textDocument/hover",
4072 "textDocument": {"uri": "${php_file_uri}"},
4073 "position": {"line": 3, "character": 1},
4076 powered_by
="serverless_ide",
4080 comment
="hover over a keyword",
4081 method
="textDocument/hover",
4083 "textDocument": {"uri": "${php_file_uri}"},
4084 "position": {"line": 2, "character": 1},
4087 powered_by
="serverless_ide",
4091 comment
="hover over a comment",
4092 method
="textDocument/hover",
4094 "textDocument": {"uri": "${php_file_uri}"},
4095 "position": {"line": 1, "character": 4},
4098 powered_by
="serverless_ide",
4102 comment
="hover past the end of a line",
4103 method
="textDocument/hover",
4105 "textDocument": {"uri": "${php_file_uri}"},
4106 "position": {"line": 3, "character": 100},
4109 powered_by
="serverless_ide",
4113 comment
="hover past the end of a file",
4114 method
="textDocument/hover",
4116 "textDocument": {"uri": "${php_file_uri}"},
4117 "position": {"line": 300, "character": 0},
4120 powered_by
="serverless_ide",
4124 comment
="hover over class with copyright docblock",
4125 method
="textDocument/hover",
4127 "textDocument": {"uri": "${php_file_uri}"},
4128 "position": {"line": 37, "character": 15},
4132 {"language": "hack", "value": "final class CopyrightClass"},
4133 "Testing copyright removal",
4136 "start": {"line": 37, "character": 2},
4137 "end": {"line": 37, "character": 16},
4140 powered_by
="serverless_ide",
4144 comment
="hover over class with generated docblock",
4145 method
="textDocument/hover",
4147 "textDocument": {"uri": "${php_file_uri}"},
4148 "position": {"line": 58, "character": 15},
4152 {"language": "hack", "value": "final class GeneratedClass"},
4153 "Testing generated text removal",
4156 "start": {"line": 58, "character": 2},
4157 "end": {"line": 58, "character": 16},
4160 powered_by
="serverless_ide",
4164 comment
="hover over an primitive attribute in an xhp literal",
4165 method
="textDocument/hover",
4167 "textDocument": {"uri": "${php_file_uri}"},
4168 "position": {"line": 62, "character": 25},
4172 {"language": "hack", "value": "public ?string name"},
4173 ":xhp:enum-attribute::name docblock",
4176 "start": {"line": 62, "character": 22},
4177 "end": {"line": 62, "character": 26},
4180 powered_by
="serverless_ide",
4184 comment
="hover over a nonprimitive attribute in an xhp literal",
4185 method
="textDocument/hover",
4187 "textDocument": {"uri": "${php_file_uri}"},
4188 "position": {"line": 62, "character": 36},
4192 {"language": "hack", "value": "public ?MyEnum enum-attribute"}
4195 "start": {"line": 62, "character": 33},
4196 "end": {"line": 62, "character": 47},
4199 powered_by
="serverless_ide",
4203 comment
="hover over a generic attribute in an xhp literal",
4204 method
="textDocument/hover",
4206 "textDocument": {"uri": "${php_file_uri}"},
4207 "position": {"line": 63, "character": 16},
4211 {"language": "hack", "value": "public ?ID<EntSomething> id"}
4214 "start": {"line": 63, "character": 15},
4215 "end": {"line": 63, "character": 17},
4218 powered_by
="serverless_ide",
4221 comment
="Add '<xhp:enum-attribute name' to test hover for incomplete xhp attribute",
4222 method
="textDocument/didChange",
4224 "textDocument": {"uri": "${php_file_uri}"},
4228 "start": {"line": 69, "character": 0},
4229 "end": {"line": 69, "character": 0},
4231 "text": "<xhp:enum-attribute name",
4238 comment
="hover over an attribute in an xhp literal without a value",
4239 method
="textDocument/hover",
4241 "textDocument": {"uri": "${php_file_uri}"},
4242 "position": {"line": 69, "character": 22},
4246 {"language": "hack", "value": "public ?string name"},
4247 ":xhp:enum-attribute::name docblock",
4250 "start": {"line": 69, "character": 20},
4251 "end": {"line": 69, "character": 24},
4254 powered_by
="serverless_ide",
4256 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4257 .notification(method
="exit", params
={})
4259 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4261 def test_serverless_ide_file_touched_on_disk(self
) -> None:
4262 variables
= dict(self
.prepare_serverless_ide_environment())
4263 variables
.update(self
.setup_php_file("hover.php"))
4264 self
.test_driver
.stop_hh_server()
4267 self
.initialize_spec(
4268 LspTestSpec("serverless_ide_file_on_disk_change"),
4269 use_serverless_ide
=True,
4272 method
="textDocument/didOpen",
4275 "uri": "${php_file_uri}",
4276 "languageId": "hack",
4278 "text": "${php_file}",
4283 method
="workspace/didChangeWatchedFiles",
4284 params
={"changes": [{"uri": "${php_file_uri}", "type": 2}]},
4286 .wait_for_notification(
4287 comment
="wait for sIDE to process file change",
4288 method
="telemetry/event",
4291 "message": "[client-ide] Done processing file changes",
4296 method
="textDocument/hover",
4298 "textDocument": {"uri": "${php_file_uri}"},
4299 "position": {"line": 3, "character": 16},
4303 {"language": "hack", "value": "int"},
4304 "A comment describing b_hover.",
4307 "start": {"line": 3, "character": 9},
4308 "end": {"line": 3, "character": 16},
4311 powered_by
="serverless_ide",
4313 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4314 .notification(method
="exit", params
={})
4316 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4318 def test_serverless_ide_file_hover_with_errors(self
) -> None:
4319 variables
= dict(self
.prepare_serverless_ide_environment())
4320 variables
.update(self
.setup_php_file("hover_with_errors.php"))
4321 self
.test_driver
.stop_hh_server()
4324 self
.initialize_spec(
4325 LspTestSpec("serverless_ide_hover_with_errors"), use_serverless_ide
=True
4328 method
="textDocument/didOpen",
4331 "uri": "${php_file_uri}",
4332 "languageId": "hack",
4334 "text": "${php_file}",
4339 method
="workspace/didChangeWatchedFiles",
4340 params
={"changes": [{"uri": "${php_file_uri}", "type": 2}]},
4342 .wait_for_notification(
4343 comment
="wait for sIDE to process file change",
4344 method
="telemetry/event",
4347 "message": "[client-ide] Done processing file changes",
4352 comment
="Totally normal hover",
4353 method
="textDocument/hover",
4355 "textDocument": {"uri": "${php_file_uri}"},
4356 "position": {"line": 14, "character": 37},
4362 "value": "public static function staticMethod(string $z): void",
4364 'During testing, we\'ll remove the "public" tag from this '
4366 "to ensure that we can still get IDE services",
4367 "Return type: `void`",
4368 "Full name: `HoverWithErrorsClass::staticMethod`",
4371 "end": {"character": 39, "line": 14},
4372 "start": {"character": 27, "line": 14},
4375 powered_by
="serverless_ide",
4378 comment
="Remove the 'public' visibility modifier which triggers AST->AAST errors",
4379 method
="textDocument/didChange",
4381 "textDocument": {"uri": "${php_file_uri}"},
4385 "start": {"line": 10, "character": 2},
4386 "end": {"line": 10, "character": 8},
4395 comment
="Hover should still work even if visibility modifier has been removed",
4396 method
="textDocument/hover",
4398 "textDocument": {"uri": "${php_file_uri}"},
4399 "position": {"line": 14, "character": 37},
4405 "value": "public static function staticMethod(string $z): void",
4407 'During testing, we\'ll remove the "public" tag from this '
4409 "to ensure that we can still get IDE services",
4410 "Return type: `void`",
4411 "Full name: `HoverWithErrorsClass::staticMethod`",
4414 "end": {"character": 39, "line": 14},
4415 "start": {"character": 27, "line": 14},
4418 powered_by
="serverless_ide",
4420 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4421 .notification(method
="exit", params
={})
4423 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4425 def test_serverless_ide_formatting(self
) -> None:
4426 # This test will fail if hackfmt can't be found
4427 if not self
.test_driver
.run_hackfmt_check():
4428 raise unittest
.SkipTest("Hackfmt can't be found. Skipping.")
4430 variables
= dict(self
.prepare_serverless_ide_environment())
4431 variables
.update(self
.setup_php_file("messy.php"))
4433 self
.test_driver
.stop_hh_server()
4436 self
.initialize_spec(LspTestSpec("formatting"), use_serverless_ide
=True)
4438 method
="textDocument/didOpen",
4441 "uri": "${php_file_uri}",
4442 "languageId": "hack",
4444 "text": "${php_file}",
4450 method
="textDocument/formatting",
4452 "textDocument": {"uri": "${php_file_uri}"},
4453 "options": {"tabSize": 5, "insertSpaces": True},
4458 "start": {"line": 0, "character": 0},
4459 "end": {"line": 12, "character": 0},
4461 "newText": "<?hh //strict\n\nfunction x(): string {\n"
4462 + ' $a = "this";\n\n'
4464 + ' $c = "messy";\n\n'
4466 + ' return "$a"."$b"."$c"."d";\n}\n',
4470 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4471 .notification(method
="exit", params
={})
4473 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4475 def test_serverless_ide_rangeformatting(self
) -> None:
4476 # This test will fail if hackfmt can't be found
4477 if not self
.test_driver
.run_hackfmt_check():
4478 raise unittest
.SkipTest("Hackfmt can't be found. Skipping.")
4480 variables
= dict(self
.prepare_serverless_ide_environment())
4481 variables
.update(self
.setup_php_file("messy.php"))
4483 self
.test_driver
.stop_hh_server()
4486 self
.initialize_spec(
4487 LspTestSpec("range_formatting"), use_serverless_ide
=True
4490 method
="textDocument/didOpen",
4493 "uri": "${php_file_uri}",
4494 "languageId": "hack",
4496 "text": "${php_file}",
4502 method
="textDocument/rangeFormatting",
4504 "textDocument": {"uri": "${php_file_uri}"},
4506 "start": {"line": 3, "character": 0},
4507 "end": {"line": 4, "character": 0},
4509 "options": {"tabSize": 5, "insertSpaces": True},
4514 "start": {"line": 3, "character": 0},
4515 "end": {"line": 4, "character": 0},
4517 "newText": ' $a = "this";\n',
4521 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4522 .notification(method
="exit", params
={})
4524 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4526 def test_serverless_ide_ontypeformatting(self
) -> None:
4527 # This test will fail if hackfmt can't be found
4528 if not self
.test_driver
.run_hackfmt_check():
4529 raise unittest
.SkipTest("Hackfmt can't be found. Skipping.")
4531 variables
= dict(self
.prepare_serverless_ide_environment())
4532 variables
.update(self
.setup_php_file("ontypeformatting.php"))
4535 self
.initialize_spec(
4536 LspTestSpec("ontypeformatting"), use_serverless_ide
=True
4539 method
="textDocument/didOpen",
4542 "uri": "${php_file_uri}",
4543 "languageId": "hack",
4545 "text": "${php_file}",
4551 method
="textDocument/onTypeFormatting",
4553 "textDocument": {"uri": "${php_file_uri}"},
4554 "position": {"line": 9, "character": 58},
4556 "options": {"tabSize": 2, "insertSpaces": True},
4561 "start": {"line": 5, "character": 23},
4562 "end": {"line": 9, "character": 58},
4564 "newText": "{\n test_otf(\n"
4565 + " '1234567890',\n"
4566 + " '1234567890',\n"
4567 + " '1234567890',\n"
4568 + " '1234567890',\n"
4569 + " '1234567890',\n"
4570 + " '1234567890',\n );",
4576 method
="textDocument/onTypeFormatting",
4578 "textDocument": {"uri": "${php_file_uri}"},
4579 "position": {"line": 15, "character": 23},
4581 "options": {"tabSize": 2, "insertSpaces": True},
4586 "start": {"line": 15, "character": 0},
4587 "end": {"line": 15, "character": 23},
4589 "newText": "function otf(): void {}",
4593 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4594 .notification(method
="exit", params
={})
4597 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4599 def test_did_change(self
) -> None:
4600 self
.prepare_server_environment()
4601 variables
= self
.setup_php_file("didchange.php")
4603 self
.initialize_spec(LspTestSpec("did_change"), use_serverless_ide
=False)
4604 .wait_for_hh_server_ready()
4606 method
="textDocument/didOpen",
4609 "uri": "${php_file_uri}",
4610 "languageId": "hack",
4612 "text": "${php_file}",
4617 method
="textDocument/didChange",
4619 "textDocument": {"uri": "${php_file_uri}"},
4623 "start": {"line": 7, "character": 11},
4624 "end": {"line": 7, "character": 12},
4631 .wait_for_notification(
4632 method
="textDocument/publishDiagnostics",
4634 "uri": "${php_file_uri}",
4638 "start": {"line": 7, "character": 11},
4639 "end": {"line": 7, "character": 11},
4644 "message": "A semicolon ; is expected here.",
4645 "relatedLocations": [],
4646 "relatedInformation": [],
4651 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4652 .wait_for_notification(
4653 comment
="Hack appears to clear out diagnostics before shutting down",
4654 method
="textDocument/publishDiagnostics",
4655 params
={"uri": "${php_file_uri}", "diagnostics": []},
4657 .notification(method
="exit", params
={})
4659 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
4661 def test_go_to_implementation(self
) -> None:
4662 self
.prepare_server_environment()
4663 variables
= self
.setup_php_file("go_to_implementation.php")
4665 self
.initialize_spec(
4666 LspTestSpec("test_go_to_implementation"), use_serverless_ide
=False
4668 .wait_for_hh_server_ready()
4670 method
="textDocument/didOpen",
4673 "uri": "${php_file_uri}",
4674 "languageId": "hack",
4676 "text": "${php_file}",
4682 comment
="go to implemenetation: abstract class",
4683 method
="textDocument/implementation",
4685 "textDocument": {"uri": "${php_file_uri}"},
4686 "position": {"line": 1, "character": 17},
4690 "uri": "${php_file_uri}",
4692 "start": {"line": 7, "character": 6},
4693 "end": {"line": 7, "character": 9},
4700 comment
="go to implemenetation: interface",
4701 method
="textDocument/implementation",
4703 "textDocument": {"uri": "${php_file_uri}"},
4704 "position": {"line": 13, "character": 13},
4708 "uri": "${php_file_uri}",
4710 "start": {"line": 17, "character": 6},
4711 "end": {"line": 17, "character": 9},
4718 comment
="go to implemenetation: trait",
4719 method
="textDocument/implementation",
4721 "textDocument": {"uri": "${php_file_uri}"},
4722 "position": {"line": 23, "character": 10},
4726 "uri": "${php_file_uri}",
4728 "start": {"line": 30, "character": 6},
4729 "end": {"line": 30, "character": 16},
4736 comment
="go to implemenetation: method",
4737 method
="textDocument/implementation",
4739 "textDocument": {"uri": "${php_file_uri}"},
4740 "position": {"line": 19, "character": 18},
4744 "uri": "${php_file_uri}",
4746 "start": {"line": 8, "character": 18},
4747 "end": {"line": 8, "character": 22},
4752 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4753 .notification(method
="exit", params
={})
4755 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
4757 def test_signature_help(self
) -> None:
4758 self
.prepare_server_environment()
4759 variables
= self
.setup_php_file("signaturehelp.php")
4761 self
.initialize_spec(
4762 LspTestSpec("test_signature_help"), use_serverless_ide
=False
4764 .wait_for_hh_server_ready()
4766 method
="textDocument/didOpen",
4769 "uri": "${php_file_uri}",
4770 "languageId": "hack",
4772 "text": "${php_file}",
4778 comment
="signature help for 0-argument constructor"
4779 " (left of opening paren)",
4780 method
="textDocument/signatureHelp",
4782 "textDocument": {"uri": "${php_file_uri}"},
4783 "position": {"line": 16, "character": 18},
4789 comment
="signature help for 0-argument constructor",
4790 method
="textDocument/signatureHelp",
4792 "textDocument": {"uri": "${php_file_uri}"},
4793 "position": {"line": 16, "character": 19},
4798 "label": "public function __construct(): void",
4799 "documentation": "Constructor with doc block",
4803 "activeSignature": 0,
4804 "activeParameter": 0,
4809 comment
="signature help for 0-argument constructor"
4810 " (right of closing paren)",
4811 method
="textDocument/signatureHelp",
4813 "textDocument": {"uri": "${php_file_uri}"},
4814 "position": {"line": 16, "character": 20},
4820 comment
="signature help for 2-argument instance method"
4821 " (left of opening paren)",
4822 method
="textDocument/signatureHelp",
4824 "textDocument": {"uri": "${php_file_uri}"},
4825 "position": {"line": 17, "character": 20},
4831 comment
="signature help for 2-argument instance method"
4832 " (right of opening paren)",
4833 method
="textDocument/signatureHelp",
4835 "textDocument": {"uri": "${php_file_uri}"},
4836 "position": {"line": 17, "character": 21},
4841 "label": "public function instanceMethod"
4842 "(int $x1, int $x2): void",
4843 "documentation": "Instance method with doc block",
4844 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4847 "activeSignature": 0,
4848 "activeParameter": 0,
4853 comment
="signature help for 2-argument instance method"
4854 " (left of first comma)",
4855 method
="textDocument/signatureHelp",
4857 "textDocument": {"uri": "${php_file_uri}"},
4858 "position": {"line": 17, "character": 22},
4863 "label": "public function instanceMethod"
4864 "(int $x1, int $x2): void",
4865 "documentation": "Instance method with doc block",
4866 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4869 "activeSignature": 0,
4870 "activeParameter": 1,
4875 comment
="signature help for 2-argument instance method"
4876 " (right of first comma)",
4877 method
="textDocument/signatureHelp",
4879 "textDocument": {"uri": "${php_file_uri}"},
4880 "position": {"line": 17, "character": 23},
4885 "label": "public function instanceMethod"
4886 "(int $x1, int $x2): void",
4887 "documentation": "Instance method with doc block",
4888 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4891 "activeSignature": 0,
4892 "activeParameter": 1,
4897 comment
="signature help for 2-argument instance method"
4898 " (left of closing paren)",
4899 method
="textDocument/signatureHelp",
4901 "textDocument": {"uri": "${php_file_uri}"},
4902 "position": {"line": 17, "character": 24},
4907 "label": "public function instanceMethod"
4908 "(int $x1, int $x2): void",
4909 "documentation": "Instance method with doc block",
4910 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4913 "activeSignature": 0,
4914 "activeParameter": 1,
4919 comment
="signature help for 2-argument instance method"
4920 " (right of closing paren)",
4921 method
="textDocument/signatureHelp",
4923 "textDocument": {"uri": "${php_file_uri}"},
4924 "position": {"line": 17, "character": 25},
4930 comment
="signature help for 1-argument static method"
4931 " (left of open paren)",
4932 method
="textDocument/signatureHelp",
4934 "textDocument": {"uri": "${php_file_uri}"},
4935 "position": {"line": 18, "character": 23},
4941 comment
="signature help for 1-argument static method"
4942 " (right of open paren)",
4943 method
="textDocument/signatureHelp",
4945 "textDocument": {"uri": "${php_file_uri}"},
4946 "position": {"line": 18, "character": 24},
4951 "label": "public static function staticMethod"
4952 "(string $z): void",
4953 "documentation": "Static method with doc block",
4954 "parameters": [{"label": "$z"}],
4957 "activeSignature": 0,
4958 "activeParameter": 0,
4963 comment
="signature help for 2-argument global function"
4964 " (left of open paren)",
4965 method
="textDocument/signatureHelp",
4967 "textDocument": {"uri": "${php_file_uri}"},
4968 "position": {"line": 19, "character": 17},
4974 comment
="signature help for 2-argument global function"
4975 " (right of open paren)",
4976 method
="textDocument/signatureHelp",
4978 "textDocument": {"uri": "${php_file_uri}"},
4979 "position": {"line": 19, "character": 18},
4984 "label": "function global_function"
4985 "(string $s, int $x): void",
4986 "documentation": "Global function with doc block",
4987 "parameters": [{"label": "$s"}, {"label": "$x"}],
4990 "activeSignature": 0,
4991 "activeParameter": 0,
4996 comment
="signature help for 1-argument namespace-aliased global"
4997 " function (right of open paren)",
4998 method
="textDocument/signatureHelp",
5000 "textDocument": {"uri": "${php_file_uri}"},
5001 "position": {"line": 20, "character": 26},
5007 comment
="signature help for 1-argument namespace-aliased global"
5008 " function (right of open paren)",
5009 method
="textDocument/signatureHelp",
5011 "textDocument": {"uri": "${php_file_uri}"},
5012 "position": {"line": 20, "character": 26},
5018 comment
="signature help for 1-argument namespace-aliased global"
5019 " function (right of open paren)",
5020 method
="textDocument/signatureHelp",
5022 "textDocument": {"uri": "${php_file_uri}"},
5023 "position": {"line": 20, "character": 27},
5028 "label": "function Derp\\Lib\\Herp\\aliased_global_func(string $s): void",
5029 "documentation": "Namespace-aliased function with doc block",
5030 "parameters": [{"label": "$s"}],
5033 "activeSignature": 0,
5034 "activeParameter": 0,
5039 comment
="signature help for 1-argument namespace-aliased global"
5040 " function (right of open paren)",
5041 method
="textDocument/signatureHelp",
5043 "textDocument": {"uri": "${php_file_uri}"},
5044 "position": {"line": 20, "character": 28},
5049 "label": "function Derp\\Lib\\Herp\\aliased_global_func(string $s): void",
5050 "documentation": "Namespace-aliased function with doc block",
5051 "parameters": [{"label": "$s"}],
5054 "activeSignature": 0,
5055 "activeParameter": 0,
5060 comment
="signature help for 2-argument function with params"
5061 " (right of open paren)",
5062 method
="textDocument/signatureHelp",
5064 "textDocument": {"uri": "${php_file_uri}"},
5065 "position": {"line": 21, "character": 30},
5070 "label": "function test_signature_help_params1("
5071 "\n string $param1,\n string $param2\n): void",
5072 "documentation": "comment describing the method"
5073 "\n@param $param1 info1"
5074 "\n@param param2 info2",
5076 {"label": "$param1", "documentation": "info1"},
5077 {"label": "$param2", "documentation": "info2"},
5081 "activeSignature": 0,
5082 "activeParameter": 0,
5087 comment
="signature help for 2-argument function with params"
5088 " (right of open paren)",
5089 method
="textDocument/signatureHelp",
5091 "textDocument": {"uri": "${php_file_uri}"},
5092 "position": {"line": 22, "character": 30},
5097 "label": "function test_signature_help_params2("
5098 "\n string $param1,\n string $param2\n): void",
5099 "documentation": "comment describing the method"
5100 "\n@param $param1 info1",
5102 {"label": "$param1", "documentation": "info1"},
5103 {"label": "$param2"},
5107 "activeSignature": 0,
5108 "activeParameter": 0,
5113 comment
="signature help for 2-argument function with params"
5114 " (right of open paren)",
5115 method
="textDocument/signatureHelp",
5117 "textDocument": {"uri": "${php_file_uri}"},
5118 "position": {"line": 23, "character": 30},
5123 "label": "function test_signature_help_params3("
5124 "\n string $param1,\n string $param2\n): string",
5125 "documentation": "@param $param1 info1"
5127 "\n@param $param2 info2"
5128 "\n@return the string"
5133 "documentation": "info1 for param1",
5135 {"label": "$param2", "documentation": "info2"},
5139 "activeSignature": 0,
5140 "activeParameter": 0,
5143 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5144 .notification(method
="exit", params
={})
5146 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
5148 def test_signature_help_lambda(self
) -> None:
5149 self
.prepare_server_environment()
5150 variables
= self
.setup_php_file("signaturehelp_lambda.php")
5152 self
.initialize_spec(
5153 LspTestSpec("test_serverless_ide_signature_help_lambda"),
5154 use_serverless_ide
=False,
5156 .wait_for_hh_server_ready()
5158 method
="textDocument/didOpen",
5161 "uri": "${php_file_uri}",
5162 "languageId": "hack",
5164 "text": "${php_file}",
5170 comment
="signature help for a normal function call",
5171 method
="textDocument/signatureHelp",
5173 "textDocument": {"uri": "${php_file_uri}"},
5174 "position": {"line": 8, "character": 29},
5177 "activeParameter": 0,
5178 "activeSignature": 0,
5181 "label": "function test_lambda_sighelp(\n"
5183 " (function(string): int) $f\n"
5185 "parameters": [{"label": "$str"}, {"label": "$f"}],
5192 comment
="signature help for normal function call within a lambda",
5193 method
="textDocument/signatureHelp",
5195 "textDocument": {"uri": "${php_file_uri}"},
5196 "position": {"line": 9, "character": 21},
5199 "activeParameter": 0,
5200 "activeSignature": 0,
5203 "label": "function normal_test_func(string $str): void",
5204 "parameters": [{"label": "$str"}],
5211 comment
="signature help for text within a lambda, left side of an open paren",
5212 method
="textDocument/signatureHelp",
5214 "textDocument": {"uri": "${php_file_uri}"},
5215 "position": {"line": 10, "character": 15},
5221 comment
="signature help for text within a lambda, right side of an open paren",
5222 method
="textDocument/signatureHelp",
5224 "textDocument": {"uri": "${php_file_uri}"},
5225 "position": {"line": 10, "character": 16},
5229 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5230 .notification(method
="exit", params
={})
5232 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
5234 def test_rename(self
) -> None:
5235 self
.prepare_server_environment()
5236 variables
= self
.setup_php_file("rename.php")
5237 self
.load_and_run("rename", variables
)
5239 def test_references(self
) -> None:
5240 self
.prepare_server_environment()
5241 variables
= self
.setup_php_file("references.php")
5242 self
.load_and_run("references", variables
)
5244 def test_non_existing_method(self
) -> None:
5245 self
.prepare_server_environment()
5246 variables
= self
.setup_php_file("nomethod.php")
5247 self
.load_and_run("nomethod", variables
)
5249 def test_bad_call(self
) -> None:
5250 self
.prepare_server_environment()
5251 variables
= self
.setup_php_file("bad_call.php")
5252 self
.load_and_run("bad_call", variables
)
5254 def test_code_action_missing_method(self
) -> None:
5255 variables
= dict(self
.prepare_serverless_ide_environment())
5256 variables
.update(self
.setup_php_file("code_action_missing_method.php"))
5257 self
.test_driver
.stop_hh_server()
5260 self
.initialize_spec(
5261 LspTestSpec("code_action_missing_method"), use_serverless_ide
=True
5264 method
="textDocument/didOpen",
5267 "uri": "${php_file_uri}",
5268 "languageId": "hack",
5270 "text": "${php_file}",
5275 comment
="make local, unsaved change to the file",
5276 method
="textDocument/didChange",
5278 "textDocument": {"uri": "${php_file_uri}", "version": 2},
5284 class ClassWithFooBar {
5285 public function foobar(): void {}
5288 function call_method(ClassWithFooBar $mc): void {
5298 comment
="get actions",
5299 method
="textDocument/codeAction",
5301 "textDocument": {"uri": "${php_file_uri}"},
5303 "start": {"line": 7, "character": 7},
5304 "end": {"line": 7, "character": 13},
5310 "start": {"line": 7, "character": 7},
5311 "end": {"line": 7, "character": 13},
5316 "message": "No instance method foobaz in ClassWithFooBar",
5317 "relatedInformation": [
5320 "uri": "${php_file_uri}",
5322 "start": {"line": 3, "character": 18},
5323 "end": {"line": 3, "character": 24},
5326 "message": "Did you mean foobar instead?",
5330 "uri": "${php_file_uri}",
5332 "start": {"line": 6, "character": 21},
5333 "end": {"line": 6, "character": 36},
5336 "message": "This is why I think it is an object of type ClassWithFooBar",
5340 "uri": "${php_file_uri}",
5342 "start": {"line": 2, "character": 6},
5343 "end": {"line": 2, "character": 21},
5346 "message": "Declaration of ClassWithFooBar is here",
5349 "relatedLocations": [
5352 "uri": "${php_file_uri}",
5354 "start": {"line": 3, "character": 18},
5355 "end": {"line": 3, "character": 24},
5358 "message": "Did you mean foobar instead?",
5362 "uri": "${php_file_uri}",
5364 "start": {"line": 6, "character": 21},
5365 "end": {"line": 6, "character": 36},
5368 "message": "This is why I think it is an object of type ClassWithFooBar",
5372 "uri": "${php_file_uri}",
5374 "start": {"line": 2, "character": 6},
5375 "end": {"line": 2, "character": 21},
5378 "message": "Declaration of ClassWithFooBar is here",
5387 "title": "Change to ->foobar",
5392 "${root_path}/code_action_missing_method.php": [
5395 "start": {"line": 7, "character": 7},
5396 "end": {"line": 7, "character": 13},
5398 "newText": "foobar",
5405 powered_by
="serverless_ide",
5407 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5408 .notification(method
="exit", params
={})
5410 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5412 def test_non_blocking(self
) -> None:
5413 self
.prepare_server_environment()
5414 variables
= self
.setup_php_file("non_blocking.php")
5415 self
.test_driver
.start_hh_loop_forever_assert_timeout()
5417 self
.initialize_spec(LspTestSpec("non_blocking"), use_serverless_ide
=False)
5418 .wait_for_hh_server_ready()
5421 method
="textDocument/definition",
5423 "textDocument": {"uri": "${php_file_uri}"},
5424 "position": {"line": 7, "character": 11},
5428 "uri": "file://${root_path}/non_blocking.php",
5430 "start": {"line": 2, "character": 9},
5431 "end": {"line": 2, "character": 32},
5433 "title": "non_blocking_definition",
5436 wait_id
="definition request",
5439 comment
="remove hh_loop_forever() invocation to break the infinite loop",
5440 method
="textDocument/didOpen",
5443 "uri": "${root_path}/__hh_loop_forever_foo.php",
5444 "languageId": "hack",
5449 function __hh_loop_forever_foo(): int {
5456 .wait_for_response(wait_id
="definition request")
5457 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5458 .notification(method
="exit", params
={})
5460 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
5462 def test_serverless_ide_hierarchy_file_change_on_disk(self
) -> None:
5463 variables
= dict(self
.prepare_serverless_ide_environment())
5464 variables
.update(self
.setup_php_file("incremental_derived.php"))
5465 changed_php_file_uri
= self
.repo_file("incremental_base.php")
5466 variables
.update({"changed_php_file_uri": changed_php_file_uri
})
5467 self
.test_driver
.stop_hh_server()
5470 self
.initialize_spec(
5471 LspTestSpec("serverless_ide_hierarchy_file_change_on_disk"),
5472 use_serverless_ide
=True,
5475 method
="textDocument/didOpen",
5478 "uri": "${php_file_uri}",
5479 "languageId": "hack",
5481 "text": "${php_file}",
5487 comment
="hover before change to class hierarchy should be `int`",
5488 method
="textDocument/hover",
5490 "textDocument": {"uri": "${php_file_uri}"},
5491 "position": {"line": 7, "character": 14},
5495 {"language": "hack", "value": "public function foo(): int"},
5496 "Return type: `int`",
5497 "Full name: `BaseClassIncremental::foo`",
5500 "start": {"line": 7, "character": 12},
5501 "end": {"line": 7, "character": 15},
5504 powered_by
="serverless_ide",
5507 uri
=changed_php_file_uri
,
5510 class BaseClassIncremental {
5511 public function foo(): string { return ''; }
5518 comment
="hover after change to class hierarchy should be `string`",
5519 method
="textDocument/hover",
5521 "textDocument": {"uri": "${php_file_uri}"},
5522 "position": {"line": 7, "character": 14},
5526 {"language": "hack", "value": "public function foo(): string"},
5527 "Return type: `string`",
5528 "Full name: `BaseClassIncremental::foo`",
5531 "start": {"line": 7, "character": 12},
5532 "end": {"line": 7, "character": 15},
5535 powered_by
="serverless_ide",
5537 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5538 .notification(method
="exit", params
={})
5541 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5543 def test_serverless_ide_decl_in_unsaved_buffer_changed(self
) -> None:
5544 variables
= dict(self
.prepare_serverless_ide_environment())
5545 variables
.update(self
.setup_php_file("hover.php"))
5546 self
.test_driver
.stop_hh_server()
5549 self
.initialize_spec(
5550 LspTestSpec("serverless_ide_decl_in_unsaved_buffer_changed"),
5551 use_serverless_ide
=True,
5554 method
="textDocument/didOpen",
5557 "uri": "${php_file_uri}",
5558 "languageId": "hack",
5560 "text": "${php_file}",
5566 comment
="hover over function invocation",
5567 method
="textDocument/hover",
5569 "textDocument": {"uri": "${php_file_uri}"},
5570 "position": {"line": 3, "character": 16},
5574 {"language": "hack", "value": "int"},
5575 "A comment describing b_hover.",
5578 "start": {"line": 3, "character": 9},
5579 "end": {"line": 3, "character": 16},
5582 powered_by
="serverless_ide",
5585 comment
="make local, unsaved change to the file",
5586 method
="textDocument/didChange",
5588 "textDocument": {"uri": "${php_file_uri}", "version": 2},
5594 function a_hover(): int {
5597 // A comment describing b_hover differently.
5598 function b_hover(): string {
5608 comment
="another hover over function invocation, should be string now",
5609 method
="textDocument/hover",
5611 "textDocument": {"uri": "${php_file_uri}"},
5612 "position": {"line": 3, "character": 16},
5616 {"language": "hack", "value": "string"},
5617 "A comment describing b_hover differently.",
5620 "start": {"line": 3, "character": 9},
5621 "end": {"line": 3, "character": 16},
5624 powered_by
="serverless_ide",
5626 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5627 .notification(method
="exit", params
={})
5630 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5632 def test_serverless_ide_decl_two_unsaved_buffers(self
) -> None:
5633 variables
= dict(self
.prepare_serverless_ide_environment())
5634 variables
.update(self
.setup_php_file("unsaved1.php"))
5635 variables
.update({"unsaved2_file_uri": self
.repo_file_uri("unsaved2.php")})
5636 self
.test_driver
.stop_hh_server()
5639 self
.initialize_spec(
5640 LspTestSpec("test_serverless_ide_decl_two_unsaved_buffers"),
5641 use_serverless_ide
=True,
5644 comment
="open 'unsaved1.php', since we'll be hovering in it",
5645 method
="textDocument/didOpen",
5648 "uri": "${php_file_uri}",
5649 "languageId": "hack",
5651 "text": "${php_file}",
5656 comment
="open 'unsaved2.php' with a bool-returning signature, different from disk",
5657 method
="textDocument/didOpen",
5660 "uri": "${unsaved2_file_uri}",
5661 "languageId": "hack",
5665 function unsaved_bar(): bool { return true; }
5672 comment
="hover 'unsaved1.php' is with respect to disk contents of 'unsaved2.php'",
5673 method
="textDocument/hover",
5675 "textDocument": {"uri": "${php_file_uri}"},
5676 "position": {"line": 1, "character": 39},
5680 {"language": "hack", "value": "function unsaved_bar(): int"},
5681 "Return type: `int`",
5684 "start": {"line": 1, "character": 34},
5685 "end": {"line": 1, "character": 45},
5688 powered_by
="serverless_ide",
5691 comment
="change signature in 'unsaved2.php' to return string",
5692 method
="textDocument/didChange",
5694 "textDocument": {"uri": "${unsaved2_file_uri}", "version": 2},
5699 function unsaved_bar(): string { return "hello"; }
5707 comment
="this is a dummy hover in 'unsaved2.php' just to ensure its decl is cached",
5708 method
="textDocument/hover",
5710 "textDocument": {"uri": "${unsaved2_file_uri}"},
5711 "position": {"line": 0, "character": 0},
5714 powered_by
="serverless_ide",
5718 comment
="hover 'unsaved1.php' is still with respect to disk contents of 'unsaved2.php'",
5719 method
="textDocument/hover",
5721 "textDocument": {"uri": "${php_file_uri}"},
5722 "position": {"line": 1, "character": 39},
5726 {"language": "hack", "value": "function unsaved_bar(): int"},
5727 "Return type: `int`",
5730 "start": {"line": 1, "character": 34},
5731 "end": {"line": 1, "character": 45},
5734 powered_by
="serverless_ide",
5737 comment
="save signature in 'unsaved2' to return string",
5738 uri
=variables
["unsaved2_file_uri"],
5741 function unsaved_bar(): string { return "hello"; }
5747 comment
="hover 'unsaved1.php' gets new disk contents of 'unsaved2.php'",
5748 method
="textDocument/hover",
5750 "textDocument": {"uri": "${php_file_uri}"},
5751 "position": {"line": 1, "character": 39},
5755 {"language": "hack", "value": "function unsaved_bar(): string"},
5756 "Return type: `string`",
5759 "start": {"line": 1, "character": 34},
5760 "end": {"line": 1, "character": 45},
5763 powered_by
="serverless_ide",
5765 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5766 .notification(method
="exit", params
={})
5769 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5771 def test_hover_without_file_open(self
) -> None:
5772 variables
= dict(self
.prepare_serverless_ide_environment())
5773 variables
.update(self
.setup_php_file("hover.php"))
5774 self
.test_driver
.stop_hh_server()
5777 self
.initialize_spec(
5778 LspTestSpec("test_hover_without_file_open"),
5779 use_serverless_ide
=True,
5780 supports_status
=True,
5782 .ignore_notifications(method
="textDocument/publishDiagnostics")
5784 comment
="Ignore 'initializing...' messages since they're racy",
5785 method
="window/showStatus",
5788 "actions": [{"title": "Restart hh_server"}],
5789 "message": "Hack IDE: initializing.\nhh_server: stopped.",
5790 "shortMessage": "Hack: initializing",
5794 comment
="another racy initializing, before hh_server has even responded",
5795 method
="window/showStatus",
5799 "message": "Hack IDE: initializing.",
5800 "shortMessage": "Hack: initializing",
5804 comment
="another racy initialization to ignore, again before hh_server",
5805 method
="window/showStatus",
5809 "message": "Hack IDE: ready.",
5810 "shortMessage": "Hack: ready",
5813 .wait_for_server_request(
5814 method
="window/showStatus",
5816 "actions": [{"title": "Restart hh_server"}],
5817 "message": "Hack IDE: ready.\nhh_server: stopped.",
5818 "shortMessage": "Hack: ready",
5821 result
=NoResponse(),
5825 comment
="hover before file_open will fail",
5826 method
="textDocument/hover",
5828 "textDocument": {"uri": "${php_file_uri}"},
5829 "position": {"line": 26, "character": 20},
5834 method
="textDocument/didOpen",
5837 "uri": "${php_file_uri}",
5838 "languageId": "hack",
5840 "text": "${php_file}",
5846 comment
="hover after file_open will succeed",
5847 method
="textDocument/hover",
5849 "textDocument": {"uri": "${php_file_uri}"},
5850 "position": {"line": 26, "character": 20},
5852 result
={"contents": [{"language": "hack", "value": "string"}]},
5853 powered_by
="serverless_ide",
5857 method
="$test/shutdownServerlessIde",
5860 powered_by
="serverless_ide",
5862 .wait_for_server_request(
5863 method
="window/showStatus",
5866 {"title": "Restart Hack IDE"},
5867 {"title": "Restart hh_server"},
5869 "message": "Hack IDE has failed. See Output›Hack for details.\nhh_server: stopped.",
5870 "shortMessage": "Hack: failed",
5873 result
={"title": "Restart Hack IDE"},
5875 .wait_for_server_request(
5876 method
="window/showStatus",
5878 "actions": [{"title": "Restart hh_server"}],
5879 "message": "Hack IDE: ready.\nhh_server: stopped.",
5880 "shortMessage": "Hack: ready",
5883 result
=NoResponse(),
5887 comment
="hover after restart will succeed",
5888 method
="textDocument/hover",
5890 "textDocument": {"uri": "${php_file_uri}"},
5891 "position": {"line": 26, "character": 20},
5893 result
={"contents": [{"language": "hack", "value": "string"}]},
5894 powered_by
="serverless_ide",
5897 method
="textDocument/didClose",
5898 params
={"textDocument": {"uri": "${php_file_uri}"}},
5902 comment
="hover after file_close will fail",
5903 method
="textDocument/hover",
5905 "textDocument": {"uri": "${php_file_uri}"},
5906 "position": {"line": 26, "character": 20},
5910 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5911 .notification(method
="exit", params
={})
5914 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5916 def test_hh_server_status_diagnostic(self
) -> None:
5917 variables
= dict(self
.prepare_serverless_ide_environment())
5918 variables
.update(self
.setup_php_file("unsaved1.php"))
5921 "unsaved2_file_uri": self
.repo_file_uri("unsaved2.php"),
5922 "unsaved2_file": self
.read_repo_file("unsaved2.php"),
5925 self
.test_driver
.stop_hh_server()
5928 self
.initialize_spec(
5929 LspTestSpec("test_hh_server_status_diagnostic"), use_serverless_ide
=True
5931 .ignore_status_diagnostics(False)
5933 method
="textDocument/didOpen",
5936 "uri": "${php_file_uri}",
5937 "languageId": "hack",
5939 "text": "${php_file}",
5943 .wait_for_notification(
5944 comment
="After didOpen(file1), the hh_server_status diagnostic should appear in file1",
5945 method
="textDocument/publishDiagnostics",
5947 "uri": "${php_file_uri}",
5951 "start": {"line": 0, "character": 0},
5952 "end": {"line": 0, "character": 1},
5955 "source": "hh_server",
5956 "message": "hh_server isn't running, so there may be undetected errors. Try `hh` at the command line... hh_server: stopped.",
5957 "relatedInformation": [],
5958 "relatedLocations": [],
5965 method
="textDocument/didOpen",
5968 "uri": "${unsaved2_file_uri}",
5969 "languageId": "hack",
5971 "text": "${unsaved2_file}",
5975 .wait_for_notification(
5976 comment
="After didOpen(file2), the hh_server_status diagnostic should disappear from file1",
5977 method
="textDocument/publishDiagnostics",
5979 "uri": "${php_file_uri}",
5984 .wait_for_notification(
5985 comment
="After didOpen(file2), the hh_server_status diagnostic should reappear in file2",
5986 method
="textDocument/publishDiagnostics",
5988 "uri": "${unsaved2_file_uri}",
5992 "start": {"line": 0, "character": 0},
5993 "end": {"line": 0, "character": 1},
5996 "source": "hh_server",
5997 "message": "hh_server isn't running, so there may be undetected errors. Try `hh` at the command line... hh_server: stopped.",
5998 "relatedInformation": [],
5999 "relatedLocations": [],
6006 method
="textDocument/didClose",
6007 params
={"textDocument": {"uri": "${unsaved2_file_uri}"}},
6009 .wait_for_notification(
6010 comment
="After didClose(file2), the hh_server_status diagnostic should disappear from file2",
6011 method
="textDocument/publishDiagnostics",
6013 "uri": "${unsaved2_file_uri}",
6018 .wait_for_notification(
6019 comment
="After didClose(file2), the hh_server_status diagnostic should reappear in file1",
6020 method
="textDocument/publishDiagnostics",
6022 "uri": "${php_file_uri}",
6026 "start": {"line": 0, "character": 0},
6027 "end": {"line": 0, "character": 1},
6030 "source": "hh_server",
6031 "message": "hh_server isn't running, so there may be undetected errors. Try `hh` at the command line... hh_server: stopped.",
6032 "relatedInformation": [],
6033 "relatedLocations": [],
6040 method
="textDocument/didClose",
6041 params
={"textDocument": {"uri": "${php_file_uri}"}},
6043 .wait_for_notification(
6044 comment
="After didClose(file1), the hh_server_status diagnostic should disappear from file1",
6045 method
="textDocument/publishDiagnostics",
6047 "uri": "${php_file_uri}",
6052 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6053 .notification(method
="exit", params
={})
6056 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6058 def _sanitize_gutter_line_numbers(self
, s
: str) -> str:
6059 gutter_line_number_re
= re
.compile(r
"^[ ]*[0-9]+ \|", re
.MULTILINE
)
6060 return re
.sub(gutter_line_number_re
, " XXXX |", s
)
6062 def test_lsptestspec_incorrect_request_result(self
) -> None:
6063 variables
= dict(self
.prepare_serverless_ide_environment())
6064 variables
.update(self
.setup_php_file("hover.php"))
6065 self
.test_driver
.stop_hh_server()
6068 self
.initialize_spec(
6069 LspTestSpec("test_lsptestspec_incorrect_request_result"),
6070 use_serverless_ide
=True,
6073 method
="textDocument/didOpen",
6076 "uri": "${php_file_uri}",
6077 "languageId": "hack",
6079 "text": "${php_file}",
6085 comment
="hover over function invocation",
6086 method
="textDocument/hover",
6088 "textDocument": {"uri": "${php_file_uri}"},
6089 "position": {"line": 3, "character": 16},
6093 {"language": "hack", "value": "int"},
6094 "INCORRECT COMMENT HERE",
6097 "start": {"line": 3, "character": 9},
6098 "end": {"line": 3, "character": 16},
6101 powered_by
="serverless_ide",
6103 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6104 .notification(method
="exit", params
={})
6109 variables
=variables
,
6110 wait_for_server
=False,
6111 use_serverless_ide
=True,
6113 raise AssertionError("Expected an error here")
6114 except AssertionError as e
:
6116 self
._sanitize
_gutter
_line
_numbers
(str(e
)),
6118 Test case test_lsptestspec_incorrect_request_result failed with 1 errors:
6121 Description: Request with ID 5 (comment: 'hover over function invocation') \
6122 got an incorrect result:
6124 (- is expected lines, + is actual lines)
6125 - {'contents': [{'language': 'hack', 'value': 'int'}, 'INCORRECT COMMENT HERE'],
6126 ? ---------------------------
6128 + {'contents': [{'language': 'hack', 'value': 'int'},
6129 + 'A comment describing b_hover.'],
6130 'range': {'end': {'character': 16, 'line': 3},
6131 'start': {'character': 9, 'line': 3}}}
6134 This was the associated request:
6136 hphp/hack/test/integration/test_lsp.py
6139 XXXX | comment="hover over function invocation",
6140 XXXX | method="textDocument/hover",
6142 XXXX | "textDocument": {"uri": "${php_file_uri}"},
6143 XXXX | "position": {"line": 3, "character": 16},
6146 XXXX | "contents": [
6147 XXXX | {"language": "hack", "value": "int"},
6148 XXXX | "INCORRECT COMMENT HERE",
6151 XXXX | "start": {"line": 3, "character": 9},
6152 XXXX | "end": {"line": 3, "character": 16},
6155 XXXX | powered_by="serverless_ide",
6159 1) If this was unexpected, then the language server is buggy and should be
6162 2) If this was expected, you can update your request with the following code to
6167 comment='hover over function invocation',
6168 method='textDocument/hover',
6169 params={'textDocument': {'uri': '${php_file_uri}'}, \
6170 'position': {'line': 3, 'character': 16}},
6171 result={'contents': [{'language': 'hack', 'value': 'int'}, \
6172 'A comment describing b_hover.'], \
6173 'range': {'start': {'line': 3, 'character': 9}, \
6174 'end': {'line': 3, 'character': 16}}},
6175 powered_by='serverless_ide',
6178 If you want to examine the raw LSP logs, you can check the `.sent.log` and
6179 `.received.log` files that were generated in the template repo for this test.\
6183 def test_lsptestspec_unexpected_notification(self
) -> None:
6184 self
.prepare_server_environment()
6185 variables
= self
.setup_php_file("didchange.php")
6187 self
.initialize_spec(LspTestSpec("did_change"), use_serverless_ide
=False)
6188 .wait_for_hh_server_ready()
6190 method
="textDocument/didOpen",
6193 "uri": "${php_file_uri}",
6194 "languageId": "hack",
6196 "text": "${php_file}",
6201 method
="textDocument/didChange",
6203 "textDocument": {"uri": "${php_file_uri}"},
6207 "start": {"line": 7, "character": 11},
6208 "end": {"line": 7, "character": 12},
6215 .wait_for_notification(
6216 method
="textDocument/publishDiagnostics",
6218 "uri": "${php_file_uri}",
6222 "start": {"line": 7, "character": 11},
6223 "end": {"line": 7, "character": 11},
6228 "message": "A semicolon ; is expected here.",
6229 "relatedLocations": [],
6230 "relatedInformation": [],
6235 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6236 .notification(method
="exit", params
={})
6240 spec
, variables
, wait_for_server
=True, use_serverless_ide
=False
6242 raise AssertionError("Expected an error here")
6243 except AssertionError as e
:
6245 self
._sanitize
_gutter
_line
_numbers
(str(e
)),
6247 Test case did_change failed with 1 errors:
6250 Description: An unexpected notification of type \
6251 'textDocument/publishDiagnostics' was sent by the language server.
6252 Here is the notification payload:
6255 'method': 'textDocument/publishDiagnostics',
6256 'params': {'diagnostics': [],
6257 'uri': '__PHP_FILE_URI__'}}
6260 This was the most recent request issued from the language client before it
6261 received the notification:
6263 hphp/hack/test/integration/test_lsp.py
6264 XXXX | .request(line=line(), method="shutdown", params={}, result=None)
6267 1) If this was unexpected, then the language server is buggy and should be
6270 2) If all notifications of type 'textDocument/publishDiagnostics' should be \
6271 ignored, add this directive
6272 anywhere in your test:
6274 .ignore_notifications(method='textDocument/publishDiagnostics')
6276 3) If this single instance of the notification was expected, add this directive
6277 to your test to wait for it before proceeding:
6279 .wait_for_notification(
6280 method='textDocument/publishDiagnostics',
6281 params={'uri': '${php_file_uri}', 'diagnostics': []},
6284 If you want to examine the raw LSP logs, you can check the `.sent.log` and
6285 `.received.log` files that were generated in the template repo for this test.\
6287 # There's an instance of a literal `${php_file_uri}` in there
6288 # which we don't want to change, so use a different name than
6290 .replace("__PHP_FILE_URI__", variables
["php_file_uri"]),
6293 def test_serverless_ide_highlight(self
) -> None:
6294 variables
= dict(self
.prepare_serverless_ide_environment())
6295 variables
.update(self
.setup_php_file("highlight.php"))
6296 self
.test_driver
.stop_hh_server()
6299 self
.initialize_spec(
6300 LspTestSpec("serverless_ide_highlight"), use_serverless_ide
=True
6303 method
="textDocument/didOpen",
6306 "uri": "${php_file_uri}",
6307 "languageId": "hack",
6309 "text": "${php_file}",
6315 comment
="document highlight, id 2",
6316 method
="textDocument/documentHighlight",
6318 "textDocument": {"uri": "${php_file_uri}"},
6319 "position": {"line": 3, "character": 10},
6324 "start": {"line": 3, "character": 9},
6325 "end": {"line": 3, "character": 20},
6329 powered_by
="serverless_ide",
6333 comment
="shutdown, id 3",
6339 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6341 def test_serverless_ide_coverage(self
) -> None:
6342 variables
= dict(self
.prepare_serverless_ide_environment())
6343 variables
.update(self
.setup_php_file("coverage.php"))
6344 self
.test_driver
.stop_hh_server()
6347 self
.initialize_spec(
6348 LspTestSpec("serverless_ide_coverage"), use_serverless_ide
=True
6351 method
="textDocument/didOpen",
6354 "uri": "${php_file_uri}",
6355 "languageId": "hack",
6357 "text": "${php_file}",
6363 comment
="Check type coverage",
6364 method
="textDocument/typeCoverage",
6365 params
={"textDocument": {"uri": "${php_file_uri}"}},
6367 "coveredPercent": 100,
6368 "uncoveredRanges": [],
6369 "defaultMessage": "Un-type checked code. Consider adding type annotations.",
6371 powered_by
="serverless_ide",
6381 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6383 def test_status_stopped(self
) -> None:
6384 self
.prepare_server_environment()
6385 variables
= self
.setup_php_file("hover.php")
6386 self
.test_driver
.stop_hh_server()
6389 self
.initialize_spec(
6390 LspTestSpec("status_stopped"),
6391 use_serverless_ide
=False,
6392 supports_status
=True,
6394 .wait_for_server_request(
6395 method
="window/showStatus",
6397 "shortMessage": "Hack: stopped",
6398 "message": "hh_server: stopped.",
6399 "actions": [{"title": "Restart hh_server"}],
6402 result
=NoResponse(),
6404 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6405 .notification(method
="exit", params
={})
6407 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=False)
6409 def test_status_running(self
) -> None:
6410 self
.prepare_server_environment()
6411 variables
= self
.setup_php_file("hover.php")
6414 self
.initialize_spec(
6415 LspTestSpec("status_running"),
6416 use_serverless_ide
=False,
6417 supports_status
=True,
6420 comment
="Ignore initializing... requests since they're racy",
6421 method
="window/showStatus",
6424 "shortMessage": "Hack: initializing",
6425 "message": "hh_server initializing: processing [<test> seconds]",
6429 .wait_for_server_request(
6430 method
="window/showStatus",
6431 params
={"actions": [], "message": "hh_server: ready.", "type": 3},
6432 result
=NoResponse(),
6434 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6435 .notification(method
="exit", params
={})
6437 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
6439 def test_serverless_ide_status_stopped(self
) -> None:
6440 variables
= dict(self
.prepare_serverless_ide_environment())
6441 variables
.update(self
.setup_php_file("hover.php"))
6442 self
.test_driver
.stop_hh_server()
6445 self
.initialize_spec(
6446 LspTestSpec("serverless_ide_status_stopped"),
6447 use_serverless_ide
=True,
6448 supports_status
=True,
6451 comment
="ignore initializing... messages since they're kind of racy",
6452 method
="window/showStatus",
6455 "actions": [{"title": "Restart hh_server"}],
6456 "message": "Hack IDE: initializing.\nhh_server: stopped.",
6457 "shortMessage": "Hack: initializing",
6461 comment
="another racy initialization to ignore, before hh_server has even reported its status",
6462 method
="window/showStatus",
6466 "message": "Hack IDE: initializing.",
6467 "shortMessage": "Hack: initializing",
6471 comment
="another racy initialization to ignore, again before hh_server",
6472 method
="window/showStatus",
6476 "message": "Hack IDE: ready.",
6477 "shortMessage": "Hack: ready",
6480 .wait_for_server_request(
6481 method
="window/showStatus",
6483 "message": "Hack IDE: ready.\nhh_server: stopped.",
6484 "shortMessage": "Hack: ready",
6485 "actions": [{"title": "Restart hh_server"}],
6488 result
=NoResponse(),
6490 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6491 .notification(method
="exit", params
={})
6493 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6495 def test_serverless_ide_status_restart(self
) -> None:
6496 variables
= dict(self
.prepare_serverless_ide_environment())
6497 variables
.update(self
.setup_php_file("hover.php"))
6500 self
.initialize_spec(
6501 LspTestSpec("serverless_ide_status_restart"),
6502 use_serverless_ide
=True,
6503 supports_status
=True,
6506 comment
="Ignore initializing messages since they're racy",
6507 method
="window/showStatus",
6511 "message": "Hack IDE: initializing.\nhh_server initializing: processing [<test> seconds]",
6512 "shortMessage": "Hack: initializing",
6516 comment
="Another form of initializing to ignore",
6517 method
="window/showStatus",
6521 "message": "Hack IDE: initializing.\nhh_server: ready.",
6522 "shortMessage": "Hack: initializing",
6526 comment
="Another form of initializing to ignore before we've even heard the first peep from hh_server",
6527 method
="window/showStatus",
6531 "message": "Hack IDE: initializing.",
6532 "shortMessage": "Hack: initializing",
6536 comment
="another racy initialization to ignore, again before hh_server",
6537 method
="window/showStatus",
6541 "message": "Hack IDE: ready.",
6542 "shortMessage": "Hack: ready",
6545 .wait_for_server_request(
6546 method
="window/showStatus",
6549 "message": "Hack IDE: ready.\nhh_server: ready.",
6550 "shortMessage": "Hack: ready",
6553 result
=NoResponse(),
6557 method
="$test/shutdownServerlessIde",
6560 powered_by
="serverless_ide",
6562 .wait_for_server_request(
6563 method
="window/showStatus",
6565 "actions": [{"title": "Restart Hack IDE"}],
6566 "message": "Hack IDE has failed. See Output›Hack for details.\nhh_server: ready.",
6567 "shortMessage": "Hack: failed",
6570 result
={"title": "Restart Hack IDE"},
6572 .wait_for_server_request(
6573 method
="window/showStatus",
6576 "message": "Hack IDE: ready.\nhh_server: ready.",
6577 "shortMessage": "Hack: ready",
6580 result
=NoResponse(),
6582 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6583 .notification(method
="exit", params
={})
6585 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=True)
6587 def test_serverless_ide_failed_to_load_saved_state(self
) -> None:
6588 variables
= dict(self
.prepare_serverless_ide_environment())
6589 variables
.update(self
.setup_php_file("hover.php"))
6590 assert "naming_table_saved_state_path" in variables
6591 variables
["naming_table_saved_state_path"] = "/tmp/nonexistent"
6594 self
.initialize_spec(
6595 LspTestSpec("serverless_ide_status_failed_to_load_saved_state"),
6596 use_serverless_ide
=True,
6597 supports_status
=True,
6601 comment
="Ignore initializing since they're kind of racy",
6602 method
="window/showStatus",
6606 "message": "Hack IDE: initializing.\nhh_server initializing: processing [<test> seconds]",
6607 "shortMessage": "Hack: initializing",
6611 comment
="Ignore another form of initializing",
6612 method
="window/showStatus",
6616 "message": "Hack IDE: initializing.\nhh_server: ready.",
6617 "shortMessage": "Hack: initializing",
6621 comment
="Ignore another form of initializing, from before we've even heard the first peep out of hh_server",
6622 method
="window/showStatus",
6626 "message": "Hack IDE: initializing.",
6627 "shortMessage": "Hack: initializing",
6631 comment
="Ignore another form of initializing, again before hh_server",
6632 method
="window/showStatus",
6635 "actions": [{"title": "Restart Hack IDE"}],
6636 "message": "Hack IDE has failed. See Output›Hack for details.",
6637 "shortMessage": "Hack: failed",
6640 .wait_for_notification(
6641 method
="window/logMessage",
6644 "message": "Hack IDE has failed.\nThis is unexpected.\nPlease file a bug within your IDE.\nMore details: http://dummy/HH_TEST_MODE",
6647 .wait_for_server_request(
6648 method
="window/showStatus",
6650 "actions": [{"title": "Restart Hack IDE"}],
6651 "message": "Hack IDE has failed. See Output›Hack for details.\nhh_server: ready.",
6652 "shortMessage": "Hack: failed",
6655 result
=NoResponse(),
6657 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6658 .notification(method
="exit", params
={})
6660 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=True)
6662 def test_workspace_symbol(self
) -> None:
6663 self
.prepare_server_environment()
6664 variables
= self
.setup_php_file("didchange.php")
6666 self
.initialize_spec(
6667 LspTestSpec("test_workspace_symbol"), use_serverless_ide
=False
6669 .wait_for_hh_server_ready()
6672 comment
="Look up symbols",
6673 method
="workspace/symbol",
6674 params
={"query": "TestNS\\test"},
6677 "name": "TestNS\\test_func",
6680 "uri": "file://${root_path}/completion_extras_namespace.php",
6682 "start": {"line": 4, "character": 9},
6683 "end": {"line": 4, "character": 25},
6691 comment
="Look up symbols starting with 'test_f' within multiple namespaces",
6692 method
="workspace/symbol",
6693 params
={"query": "test_f"},
6696 "name": "test_function",
6699 "uri": "file://${root_path}/completion.php",
6701 "start": {"line": 7, "character": 9},
6702 "end": {"line": 7, "character": 22},
6707 "name": "TestNS\\test_func",
6710 "uri": "file://${root_path}/completion_extras_namespace.php",
6712 "start": {"line": 4, "character": 9},
6713 "end": {"line": 4, "character": 25},
6719 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6720 .notification(method
="exit", params
={})
6722 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
6724 def test_serverless_ide_during_hh_server_restart(self
) -> None:
6725 variables
= dict(self
.prepare_serverless_ide_environment())
6726 variables
.update(self
.setup_php_file("didchange.php"))
6728 self
.initialize_spec(
6729 LspTestSpec("test_serverless_ide_during_hh_server_restart"),
6730 use_serverless_ide
=True,
6733 method
="textDocument/didOpen",
6736 "uri": "${php_file_uri}",
6737 "languageId": "hack",
6739 "text": "${php_file}",
6744 comment
="Send a 'didChange' notification before HH Server is functional.",
6745 method
="textDocument/didChange",
6747 "textDocument": {"uri": "${php_file_uri}"},
6751 "start": {"line": 7, "character": 9},
6752 "end": {"line": 7, "character": 11},
6759 .start_hh_server("Start HH Server; should detect the bad edit")
6760 .wait_for_notification(
6761 method
="textDocument/publishDiagnostics",
6763 "uri": "${php_file_uri}",
6767 "message": "Invalid return type",
6769 "end": {"character": 14, "line": 7},
6770 "start": {"character": 9, "line": 7},
6772 "relatedInformation": [
6776 "end": {"character": 27, "line": 6},
6777 "start": {"character": 24, "line": 6},
6779 "uri": "${php_file_uri}",
6781 "message": "Expected int",
6786 "end": {"character": 14, "line": 7},
6787 "start": {"character": 9, "line": 7},
6789 "uri": "${php_file_uri}",
6791 "message": "But got string",
6794 "relatedLocations": [
6798 "end": {"character": 27, "line": 6},
6799 "start": {"character": 24, "line": 6},
6801 "uri": "${php_file_uri}",
6803 "message": "Expected int",
6808 "end": {"character": 14, "line": 7},
6809 "start": {"character": 9, "line": 7},
6811 "uri": "${php_file_uri}",
6813 "message": "But got string",
6822 .stop_hh_server("Shutdown HH Server")
6823 .start_hh_server("Restart HH Server")
6824 .wait_for_notification(
6825 comment
="On startup it thinks everything is okay ...",
6826 method
="textDocument/publishDiagnostics",
6827 params
={"uri": "${php_file_uri}", "diagnostics": []},
6829 .wait_for_notification(
6830 comment
="But then hh_server sends a hello message and it gets the edited files, which leads it to see the problem.",
6831 method
="textDocument/publishDiagnostics",
6833 "uri": "${php_file_uri}",
6837 "message": "Invalid return type",
6839 "end": {"character": 14, "line": 7},
6840 "start": {"character": 9, "line": 7},
6842 "relatedInformation": [
6846 "end": {"character": 27, "line": 6},
6847 "start": {"character": 24, "line": 6},
6849 "uri": "${php_file_uri}",
6851 "message": "Expected int",
6856 "end": {"character": 14, "line": 7},
6857 "start": {"character": 9, "line": 7},
6859 "uri": "${php_file_uri}",
6861 "message": "But got string",
6864 "relatedLocations": [
6868 "end": {"character": 27, "line": 6},
6869 "start": {"character": 24, "line": 6},
6871 "uri": "${php_file_uri}",
6873 "message": "Expected int",
6878 "end": {"character": 14, "line": 7},
6879 "start": {"character": 9, "line": 7},
6881 "uri": "${php_file_uri}",
6883 "message": "But got string",
6892 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6893 .notification(method
="exit", params
={})
6895 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=True)
6897 def test_serverless_ide_naming_error1(self
) -> None:
6898 variables
= dict(self
.prepare_serverless_ide_environment())
6899 variables
.update(self
.setup_php_file("didchange.php"))
6902 "main_file": self
.repo_file("main.php"),
6903 "main_file_contents": """\
6905 function main(): int {
6909 "file_a": self
.repo_file("a.php"),
6910 "file_b": self
.repo_file("b.php"),
6914 self
.initialize_spec(
6915 LspTestSpec("serverless_ide_naming_error1"), use_serverless_ide
=True
6918 uri
="${main_file}", contents
="${main_file_contents}", notify
=True
6921 method
="textDocument/didOpen",
6924 "uri": "${main_file}",
6925 "languageId": "hack",
6927 "text": "${main_file_contents}",
6933 comment
="Ensure that hover over `aaa` works even when the name is not yet defined",
6934 method
="textDocument/hover",
6936 "textDocument": {"uri": "${main_file}"},
6937 "position": {"line": 2, "character": 13},
6940 "contents": [{"language": "hack", "value": "_"}],
6942 "start": {"line": 2, "character": 11},
6943 "end": {"line": 2, "character": 14},
6946 powered_by
="serverless_ide",
6949 comment
="create file A",
6953 function aaa(): int {
6961 comment
="Ensure that hover over `aaa` works when there are no naming errors",
6962 method
="textDocument/hover",
6964 "textDocument": {"uri": "${main_file}"},
6965 "position": {"line": 2, "character": 13},
6969 {"language": "hack", "value": "function aaa(): int"},
6970 "Return type: `int`",
6973 "start": {"line": 2, "character": 11},
6974 "end": {"line": 2, "character": 14},
6977 powered_by
="serverless_ide",
6980 comment
="create file B",
6984 function aaa(): string {
6992 comment
="Ensure that hover over `aaa` works even when there is a duplicate name",
6993 method
="textDocument/hover",
6995 "textDocument": {"uri": "${main_file}"},
6996 "position": {"line": 2, "character": 13},
7000 {"language": "hack", "value": "function aaa(): int"},
7001 "Return type: `int`",
7004 "start": {"line": 2, "character": 11},
7005 "end": {"line": 2, "character": 14},
7008 powered_by
="serverless_ide",
7011 comment
="delete file A", uri
="${file_a}", contents
=None, notify
=True
7015 comment
="Now that we've fixed the error, hover should work.",
7016 method
="textDocument/hover",
7018 "textDocument": {"uri": "${main_file}"},
7019 "position": {"line": 2, "character": 13},
7023 {"language": "hack", "value": "function aaa(): string"},
7024 "Return type: `string`",
7027 "start": {"line": 2, "character": 11},
7028 "end": {"line": 2, "character": 14},
7031 powered_by
="serverless_ide",
7033 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7034 .notification(method
="exit", params
={})
7036 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7038 def test_serverless_ide_naming_error2(self
) -> None:
7039 variables
= dict(self
.prepare_serverless_ide_environment())
7040 self
.test_driver
.stop_hh_server()
7041 variables
.update(self
.setup_php_file("naming_error_caller.php"))
7044 "contents": self
.read_repo_file("naming_error_declaration.php"),
7045 "original": self
.repo_file("naming_error_declaration.php"),
7046 "copy": self
.repo_file("naming_error_copy.php"),
7050 self
.initialize_spec(
7051 LspTestSpec("serverless_ide_naming_error2"), use_serverless_ide
=True
7054 method
="textDocument/didOpen",
7057 "uri": "${php_file_uri}",
7058 "languageId": "hack",
7060 "text": "${php_file}",
7065 comment
="create copy",
7067 contents
="${contents}",
7071 comment
="delete copy", uri
="${copy}", contents
=None, notify
=True
7075 comment
="hover should work fine after making copy then deleting copy.",
7076 method
="textDocument/hover",
7078 "textDocument": {"uri": "${php_file_uri}"},
7079 "position": {"line": 3, "character": 15},
7085 "value": "function naming_error_declaration(): void",
7087 "Return type: `void`",
7090 "start": {"line": 3, "character": 2},
7091 "end": {"line": 3, "character": 26},
7094 powered_by
="serverless_ide",
7096 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7097 .notification(method
="exit", params
={})
7099 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7101 def test_serverless_ide_naming_error3(self
) -> None:
7102 variables
= dict(self
.prepare_serverless_ide_environment())
7103 self
.test_driver
.stop_hh_server()
7104 variables
.update(self
.setup_php_file("naming_error_caller.php"))
7107 "contents": self
.read_repo_file("naming_error_declaration.php"),
7108 "original": self
.repo_file("naming_error_declaration.php"),
7109 "copy": self
.repo_file("naming_error_copy.php"),
7113 self
.initialize_spec(
7114 LspTestSpec("serverless_ide_naming_error3"), use_serverless_ide
=True
7117 method
="textDocument/didOpen",
7120 "uri": "${php_file_uri}",
7121 "languageId": "hack",
7123 "text": "${php_file}",
7128 comment
="create copy",
7130 contents
="${contents}",
7134 comment
="delete original", uri
="${original}", contents
=None, notify
=True
7138 comment
="hover should work fine after making copy then deleting original.",
7139 method
="textDocument/hover",
7141 "textDocument": {"uri": "${php_file_uri}"},
7142 "position": {"line": 3, "character": 15},
7148 "value": "function naming_error_declaration(): void",
7150 "Return type: `void`",
7153 "start": {"line": 3, "character": 2},
7154 "end": {"line": 3, "character": 26},
7157 powered_by
="serverless_ide",
7159 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7160 .notification(method
="exit", params
={})
7162 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7164 def test_serverless_ide_requests_before_init(self
) -> None:
7165 variables
= dict(self
.prepare_serverless_ide_environment())
7166 variables
["root_path"] = self
.test_driver
.repo_dir
7167 self
.test_driver
.stop_hh_server()
7170 self
.initialize_spec(
7171 LspTestSpec("test_serverless_ide_requests_before_init"),
7172 use_serverless_ide
=True,
7173 supports_status
=True,
7176 .ignore_notifications(method
="textDocument/publishDiagnostics")
7178 comment
="Ignore 'initializing...' messages since they're racy",
7179 method
="window/showStatus",
7182 "actions": [{"title": "Restart hh_server"}],
7183 "message": "Hack IDE: initializing.\nhh_server: stopped.",
7184 "shortMessage": "Hack: initializing",
7188 comment
="another racy initialization, before we've yet heard from hh_server",
7189 method
="window/showStatus",
7193 "message": "Hack IDE: initializing.",
7194 "shortMessage": "Hack: initializing",
7198 comment
="another racy initialization, if HackIDE is done before hh_server has yet sent status",
7199 method
="window/showStatus",
7203 "message": "Hack IDE: ready.",
7204 "shortMessage": "Hack: ready",
7210 uri
="file://${root_path}/beforeInit1.php",
7211 contents
="<?hh // strict\nfunction beforeInit1(): int {\n return 42;\n}\n",
7214 comment
="open a file before init has finished",
7215 method
="textDocument/didOpen",
7218 "uri": "file://${root_path}/beforeInit2.php",
7219 "languageId": "hack",
7221 "text": "<?hh // strict\nfunction beforeInit2(): void {\n $foo = beforeInit1();\n}\n",
7227 comment
="hover before init will fail",
7228 method
="textDocument/hover",
7230 "textDocument": {"uri": "file://${root_path}/beforeInit2.php"},
7231 "position": {"line": 2, "character": 4},
7237 comment
="documentSymbol before init will succeed",
7238 method
="textDocument/documentSymbol",
7239 params
={"textDocument": {"uri": "file://${root_path}/beforeInit2.php"}},
7242 "name": "beforeInit2",
7245 "uri": "file://${root_path}/beforeInit2.php",
7247 "start": {"line": 1, "character": 0},
7248 "end": {"line": 3, "character": 1},
7253 powered_by
="serverless_ide",
7255 .wait_for_notification(
7256 comment
="wait for sIDE to init",
7257 method
="telemetry/event",
7258 params
={"type": 4, "message": "[client-ide] Finished init: ok"},
7260 .wait_for_server_request(
7261 method
="window/showStatus",
7263 "actions": [{"title": "Restart hh_server"}],
7264 "message": "Hack IDE: ready.\nhh_server: stopped.",
7265 "shortMessage": "Hack: ready",
7268 result
=NoResponse(),
7272 comment
="hover after init will succeed",
7273 method
="textDocument/hover",
7275 "textDocument": {"uri": "file://${root_path}/beforeInit2.php"},
7276 "position": {"line": 2, "character": 4},
7279 "contents": [{"language": "hack", "value": "int"}],
7281 "start": {"line": 2, "character": 2},
7282 "end": {"line": 2, "character": 6},
7285 powered_by
="serverless_ide",
7287 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7288 .notification(method
="exit", params
={})
7291 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7293 def test_serverless_ide_workspace_symbol(self
) -> None:
7294 variables
= dict(self
.prepare_serverless_ide_environment())
7295 variables
["root_path"] = self
.test_driver
.repo_dir
7296 self
.test_driver
.stop_hh_server()
7299 self
.initialize_spec(
7300 LspTestSpec("serverless_ide_workspace_symbol"), use_serverless_ide
=True
7304 comment
="workspace symbol call, global, powered by sqlite (generated during serverless-ide-init)",
7305 method
="workspace/symbol",
7306 params
={"query": "TakesString"},
7309 "name": "TakesString",
7312 "uri": "file://${root_path}/definition.php",
7314 "start": {"line": 36, "character": 6},
7315 "end": {"line": 36, "character": 17},
7320 powered_by
="serverless_ide",
7324 comment
="workspace symbol call, member (derived from naming-table)",
7325 method
="workspace/symbol",
7326 params
={"query": "TakesString::"},
7329 "name": "__construct",
7332 "uri": "file://${root_path}/definition.php",
7334 "start": {"line": 37, "character": 18},
7335 "end": {"line": 37, "character": 29},
7340 powered_by
="serverless_ide",
7342 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7343 .notification(method
="exit", params
={})
7345 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)