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 '<xhp:enum-attribute enum-attribute={}'",
1872 method
="textDocument/didChange",
1874 "textDocument": {"uri": "${php_file_uri}"},
1878 "start": {"line": 3, "character": 0},
1879 "end": {"line": 3, "character": 17},
1881 "text": "<xhp:enum-attribute enum-attribute={}",
1888 comment
="autocomplete after '<xhp:enum-attribute enum-attribute={'",
1889 method
="textDocument/completion",
1891 "textDocument": {"uri": "${php_file_uri}"},
1892 "position": {"line": 3, "character": 36},
1893 "context": {"triggerKind": 2, "triggerCharacter": "{"},
1896 "isIncomplete": False,
1899 "label": "MyEnum::TYPE_C",
1902 "inlineDetail": "enum",
1903 "sortText": "MyEnum::TYPE_C",
1904 "insertText": "MyEnum::TYPE_C",
1905 "insertTextFormat": 1,
1907 "fullname": "MyEnum::TYPE_C",
1908 "filename": "${root_path}/xhp_class_definitions.php",
1911 "base_class": "\\MyEnum",
1915 "label": "MyEnum::TYPE_A",
1918 "inlineDetail": "enum",
1919 "sortText": "MyEnum::TYPE_A",
1920 "insertTextFormat": 1,
1921 "insertText": "MyEnum::TYPE_A",
1923 "fullname": "MyEnum::TYPE_A",
1924 "filename": "${root_path}/xhp_class_definitions.php",
1927 "base_class": "\\MyEnum",
1931 "label": "MyEnum::TYPE_B",
1934 "inlineDetail": "enum",
1935 "sortText": "MyEnum::TYPE_B",
1936 "insertTextFormat": 1,
1937 "insertText": "MyEnum::TYPE_B",
1939 "fullname": "MyEnum::TYPE_B",
1940 "filename": "${root_path}/xhp_class_definitions.php",
1943 "base_class": "\\MyEnum",
1948 powered_by
="serverless_ide",
1951 comment
="Add '1 is strin'",
1952 method
="textDocument/didChange",
1954 "textDocument": {"uri": "${php_file_uri}"},
1958 "start": {"line": 3, "character": 0},
1959 "end": {"line": 3, "character": 37},
1961 "text": "1 is strin",
1968 comment
="autocomplete after '1 is strin'",
1969 method
="textDocument/completion",
1971 "textDocument": {"uri": "${php_file_uri}"},
1972 "position": {"line": 3, "character": 10},
1975 "isIncomplete": False,
1978 "data": {"fullname": "string"},
1979 "detail": "builtin",
1982 "value": "A sequence of zero or more characters. Strings are usually manipulated with functions from the `Str\\` namespace",
1984 "inlineDetail": "builtin",
1985 "insertText": "string",
1986 "insertTextFormat": 1,
1989 "sortText": "string",
1992 "data": {"fullname": "StringBuffer"},
1994 "inlineDetail": "class",
1995 "insertText": "StringBuffer",
1996 "insertTextFormat": 1,
1998 "label": "StringBuffer",
1999 "sortText": "StringBuffer",
2002 "data": {"fullname": "Stringish"},
2003 "detail": "interface",
2004 "inlineDetail": "interface",
2005 "insertText": "Stringish",
2006 "insertTextFormat": 1,
2008 "label": "Stringish",
2009 "sortText": "Stringish",
2012 "data": {"fullname": "StringishObject"},
2013 "detail": "interface",
2014 "inlineDetail": "interface",
2015 "insertText": "StringishObject",
2016 "insertTextFormat": 1,
2018 "label": "StringishObject",
2019 "sortText": "StringishObject",
2023 powered_by
="serverless_ide",
2027 comment
="autocomplete resolving after '1 is strin'",
2028 method
="completionItem/resolve",
2030 "data": {"fullname": "string"},
2031 "detail": "builtin",
2034 "value": "A sequence of zero or more characters. Strings are usually manipulated with functions from the `Str\\` namespace",
2036 "inlineDetail": "builtin",
2037 "insertText": "string",
2038 "insertTextFormat": 1,
2041 "sortText": "string",
2044 "data": {"fullname": "string"},
2045 "detail": "builtin",
2048 "value": "A sequence of zero or more characters. Strings are usually manipulated with functions from the `Str\\` namespace",
2050 "inlineDetail": "builtin",
2051 "insertText": "string",
2052 "insertTextFormat": 1,
2055 "sortText": "string",
2057 powered_by
="serverless_ide",
2059 .request(line
=line(), method
="shutdown", params
={}, result
=None)
2060 .notification(method
="exit", params
={})
2062 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
2064 def test_serverless_ide_completion_legacy(self
) -> None:
2065 variables
= dict(self
.prepare_serverless_ide_environment())
2066 variables
.update(self
.setup_php_file("completion.php"))
2067 self
.test_driver
.stop_hh_server()
2070 self
.initialize_spec(
2071 LspTestSpec("serverless_ide_completion_legacy"), use_serverless_ide
=True
2074 method
="textDocument/didOpen",
2077 "uri": "${php_file_uri}",
2078 "languageId": "hack",
2080 "text": "${php_file}",
2085 comment
="Add '$x = <'",
2086 method
="textDocument/didChange",
2088 "textDocument": {"uri": "${php_file_uri}"},
2092 "start": {"line": 3, "character": 0},
2093 "end": {"line": 3, "character": 0},
2102 comment
="autocomplete after '$x = <'",
2103 method
="textDocument/completion",
2105 "textDocument": {"uri": "${php_file_uri}"},
2106 "position": {"line": 3, "character": 6},
2109 "isIncomplete": False,
2112 "label": "ab:cd:alpha",
2115 "inlineDetail": "class",
2116 "sortText": "ab:cd:alpha",
2117 "insertText": "ab:cd:alpha",
2118 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2119 "data": {"fullname": ":ab:cd:alpha"},
2122 "label": "ab:cd:text",
2125 "inlineDetail": "class",
2126 "sortText": "ab:cd:text",
2127 "insertText": "ab:cd:text",
2128 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2129 "data": {"fullname": ":ab:cd:text"},
2132 "label": "xhp:enum-attribute",
2135 "inlineDetail": "class",
2136 "sortText": "xhp:enum-attribute",
2137 "insertText": "xhp:enum-attribute",
2138 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2139 "data": {"fullname": ":xhp:enum-attribute"},
2142 "label": "xhp:generic",
2145 "inlineDetail": "class",
2146 "sortText": "xhp:generic",
2147 "insertText": "xhp:generic",
2148 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2149 "data": {"fullname": ":xhp:generic"},
2153 powered_by
="serverless_ide",
2156 comment
="Add '$x = <a'",
2157 method
="textDocument/didChange",
2159 "textDocument": {"uri": "${php_file_uri}"},
2163 "start": {"line": 3, "character": 0},
2164 "end": {"line": 3, "character": 6},
2173 comment
="autocomplete after '$x = <a'",
2174 method
="textDocument/completion",
2176 "textDocument": {"uri": "${php_file_uri}"},
2177 "position": {"line": 3, "character": 7},
2180 "isIncomplete": False,
2183 "label": "ab:cd:alpha",
2186 "inlineDetail": "class",
2187 "sortText": "ab:cd:alpha",
2188 "insertText": "ab:cd:alpha",
2189 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2190 "data": {"fullname": ":ab:cd:alpha"},
2193 "label": "ab:cd:text",
2196 "inlineDetail": "class",
2197 "sortText": "ab:cd:text",
2198 "insertText": "ab:cd:text",
2199 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2200 "data": {"fullname": ":ab:cd:text"},
2204 powered_by
="serverless_ide",
2207 comment
="Add '$x = <ab:'",
2208 method
="textDocument/didChange",
2210 "textDocument": {"uri": "${php_file_uri}"},
2214 "start": {"line": 3, "character": 0},
2215 "end": {"line": 3, "character": 7},
2217 "text": "$x = <ab:",
2224 comment
="autocomplete after '$x = <ab:'.",
2225 method
="textDocument/completion",
2227 "textDocument": {"uri": "${php_file_uri}"},
2228 "position": {"line": 3, "character": 9},
2231 "isIncomplete": False,
2234 "label": "ab:cd:alpha",
2237 "inlineDetail": "class",
2238 "sortText": "ab:cd:alpha",
2239 "insertText": "ab:cd:alpha",
2240 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2241 "data": {"fullname": ":ab:cd:alpha"},
2244 "label": "ab:cd:text",
2247 "inlineDetail": "class",
2248 "sortText": "ab:cd:text",
2249 "insertText": "ab:cd:text",
2250 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2251 "data": {"fullname": ":ab:cd:text"},
2255 powered_by
="serverless_ide",
2258 comment
="Add '$x = <ab:cd:text '",
2259 method
="textDocument/didChange",
2261 "textDocument": {"uri": "${php_file_uri}"},
2265 "start": {"line": 3, "character": 0},
2266 "end": {"line": 3, "character": 9},
2268 "text": "$x = <ab:cd:text ",
2275 comment
="autocomplete after '$x = <ab:cd:text '",
2276 method
="textDocument/completion",
2278 "textDocument": {"uri": "${php_file_uri}"},
2279 "position": {"line": 3, "character": 17},
2282 "isIncomplete": False,
2288 "inlineDetail": "?int",
2289 "sortText": "width",
2290 "insertText": "width",
2291 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2293 "fullname": ":width",
2294 "filename": "${root_path}/xhp_class_definitions.php",
2297 "base_class": "\\:ab:cd:text",
2303 "detail": "?string",
2304 "inlineDetail": "?string",
2305 "sortText": "color",
2306 "insertText": "color",
2307 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2309 "fullname": ":color",
2310 "filename": "${root_path}/xhp_class_definitions.php",
2313 "base_class": "\\:ab:cd:text",
2318 powered_by
="serverless_ide",
2321 comment
="Add '$x = <ab:cd:text w'",
2322 method
="textDocument/didChange",
2324 "textDocument": {"uri": "${php_file_uri}"},
2328 "start": {"line": 3, "character": 0},
2329 "end": {"line": 3, "character": 17},
2331 "text": "$x = <ab:cd:text w",
2338 comment
="autocomplete after '$x = <ab:cd:text w'",
2339 method
="textDocument/completion",
2341 "textDocument": {"uri": "${php_file_uri}"},
2342 "position": {"line": 3, "character": 18},
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 = new :''",
2385 method
="textDocument/didChange",
2387 "textDocument": {"uri": "${php_file_uri}"},
2391 "start": {"line": 3, "character": 0},
2392 "end": {"line": 3, "character": 18},
2394 "text": "$x = new :",
2401 comment
="autocomplete after '$x = new :'",
2402 method
="textDocument/completion",
2404 "textDocument": {"uri": "${php_file_uri}"},
2405 "position": {"line": 3, "character": 10},
2408 "isIncomplete": False,
2411 "label": ":ab:cd:alpha",
2414 "inlineDetail": "class",
2415 "sortText": ":ab:cd:alpha",
2416 "insertText": ":ab:cd:alpha",
2417 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2418 "data": {"fullname": ":ab:cd:alpha"},
2421 "label": ":ab:cd:text",
2424 "inlineDetail": "class",
2425 "sortText": ":ab:cd:text",
2426 "insertText": ":ab:cd:text",
2427 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2428 "data": {"fullname": ":ab:cd:text"},
2431 "label": ":xhp:enum-attribute",
2434 "inlineDetail": "class",
2435 "sortText": ":xhp:enum-attribute",
2436 "insertText": ":xhp:enum-attribute",
2437 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2438 "data": {"fullname": ":xhp:enum-attribute"},
2441 "label": ":xhp:generic",
2444 "inlineDetail": "class",
2445 "sortText": ":xhp:generic",
2446 "insertText": ":xhp:generic",
2447 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2448 "data": {"fullname": ":xhp:generic"},
2452 powered_by
="serverless_ide",
2455 comment
="Add '$x = new :a'",
2456 method
="textDocument/didChange",
2458 "textDocument": {"uri": "${php_file_uri}"},
2462 "start": {"line": 3, "character": 0},
2463 "end": {"line": 3, "character": 10},
2465 "text": "$x = new :a",
2472 comment
="autocomplete after '$x = new :a'",
2473 method
="textDocument/completion",
2475 "textDocument": {"uri": "${php_file_uri}"},
2476 "position": {"line": 3, "character": 11},
2479 "isIncomplete": False,
2482 "label": ":ab:cd:alpha",
2485 "inlineDetail": "class",
2486 "sortText": ":ab:cd:alpha",
2487 "insertText": ":ab:cd:alpha",
2488 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2489 "data": {"fullname": ":ab:cd:alpha"},
2492 "label": ":ab:cd:text",
2495 "inlineDetail": "class",
2496 "sortText": ":ab:cd:text",
2497 "insertText": ":ab:cd:text",
2498 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2499 "data": {"fullname": ":ab:cd:text"},
2503 powered_by
="serverless_ide",
2505 # Note that this request sent should match the result given in the previous example
2508 comment
="autocomplete resolving after '$x = new :a'",
2509 method
="completionItem/resolve",
2511 "label": ":ab:cd:alpha",
2514 "inlineDetail": "class",
2515 "itemType": ":ab:cd:alpha",
2516 "insertText": ":ab:cd:alpha",
2517 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2518 "data": {"fullname": ":ab:cd:alpha"},
2521 "label": ":ab:cd:alpha",
2524 "inlineDetail": "class",
2525 "itemType": ":ab:cd:alpha",
2528 "value": ":ab:cd:alpha docblock",
2530 "insertText": ":ab:cd:alpha",
2531 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2532 "data": {"fullname": ":ab:cd:alpha"},
2534 powered_by
="serverless_ide",
2537 comment
="Add '$x = <ab:cd:text/>; $y = $x->'",
2538 method
="textDocument/didChange",
2540 "textDocument": {"uri": "${php_file_uri}"},
2544 "start": {"line": 3, "character": 0},
2545 "end": {"line": 3, "character": 11},
2547 "text": "$x = <ab:cd:text/>; $y = $x->",
2554 comment
="autocomplete after '$x = <ab:cd:text/>; $y = $x->'",
2555 method
="textDocument/completion",
2557 "textDocument": {"uri": "${php_file_uri}"},
2558 "position": {"line": 3, "character": 29},
2561 "isIncomplete": False,
2567 "inlineDetail": "?int",
2568 "sortText": ":width",
2569 "insertText": ":width",
2570 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2572 "fullname": ":width",
2573 "filename": "${root_path}/xhp_class_definitions.php",
2576 "base_class": "\\:ab:cd:text",
2582 "detail": "?string",
2583 "inlineDetail": "?string",
2584 "sortText": ":color",
2585 "insertText": ":color",
2586 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2588 "fullname": ":color",
2589 "filename": "${root_path}/xhp_class_definitions.php",
2592 "base_class": "\\:ab:cd:text",
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": 29},
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": 30},
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 'test_fun'",
2664 method
="textDocument/didChange",
2666 "textDocument": {"uri": "${php_file_uri}"},
2670 "start": {"line": 3, "character": 0},
2671 "end": {"line": 3, "character": 30},
2680 comment
="autocomplete after 'test_fun'",
2681 method
="textDocument/completion",
2683 "textDocument": {"uri": "${php_file_uri}"},
2684 "position": {"line": 3, "character": 8},
2687 "isIncomplete": False,
2690 "label": "test_function",
2692 "detail": "function",
2693 "inlineDetail": "function",
2694 "sortText": "test_function",
2695 "insertText": "test_function",
2696 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2697 "data": {"fullname": "test_function"},
2701 powered_by
="serverless_ide",
2705 comment
="autocomplete resolving after 'test_fun'",
2706 method
="completionItem/resolve",
2708 "label": "test_function",
2710 "detail": "function(): void",
2711 "inlineDetail": "()",
2713 "insertText": "test_function",
2714 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2716 "filename": "${root_path}/completion.php",
2722 "label": "test_function",
2724 "detail": "function(): void",
2725 "inlineDetail": "()",
2729 "value": "test_function docblock.",
2731 "insertText": "test_function",
2732 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2734 "filename": "${root_path}/completion.php",
2739 powered_by
="serverless_ide",
2742 comment
="Add 'switch (Elsa::Alonso) { case Elsa:'",
2743 method
="textDocument/didChange",
2745 "textDocument": {"uri": "${php_file_uri}"},
2749 "start": {"line": 3, "character": 0},
2750 "end": {"line": 3, "character": 8},
2752 "text": "switch (Elsa::Alonso) { case Elsa:",
2759 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa:'",
2760 method
="textDocument/completion",
2762 "textDocument": {"uri": "${php_file_uri}"},
2763 "position": {"line": 3, "character": 34},
2765 result
={"isIncomplete": False, "items": []},
2766 powered_by
="serverless_ide",
2769 comment
="Add 'switch (Elsa::Alonso) { case Elsa::'",
2770 method
="textDocument/didChange",
2772 "textDocument": {"uri": "${php_file_uri}"},
2776 "start": {"line": 3, "character": 0},
2777 "end": {"line": 3, "character": 34},
2779 "text": "switch (Elsa::Alonso) { case Elsa::",
2786 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa::'",
2787 method
="textDocument/completion",
2789 "textDocument": {"uri": "${php_file_uri}"},
2790 "position": {"line": 3, "character": 35},
2793 "isIncomplete": False,
2798 "detail": "classname<this>",
2799 "inlineDetail": "classname<this>",
2800 "sortText": "class",
2801 "insertText": "class",
2802 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2804 "fullname": "class",
2805 "filename": "${root_path}/completion_extras.php",
2808 "base_class": "\\Elsa",
2815 "inlineDetail": "Elsa",
2817 "insertText": "Bard",
2818 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2821 "filename": "${root_path}/completion_extras.php",
2824 "base_class": "\\Elsa",
2831 "inlineDetail": "Elsa",
2832 "sortText": "Alonso",
2833 "insertText": "Alonso",
2834 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
2836 "fullname": "Alonso",
2837 "filename": "${root_path}/completion_extras.php",
2840 "base_class": "\\Elsa",
2846 "detail": "function(mixed $value): bool",
2847 "inlineDetail": "(mixed $value)",
2849 "sortText": "isValid",
2850 "insertText": "isValid(${1:\\$value})",
2851 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2853 "fullname": "isValid",
2854 "filename": "${hhi_path}/BuiltinEnum.hhi",
2857 "base_class": "\\Elsa",
2861 "label": "getValues",
2863 "detail": "function(): dict<string, Elsa>",
2864 "inlineDetail": "()",
2865 "itemType": "dict<string, Elsa>",
2866 "sortText": "getValues",
2867 "insertText": "getValues()",
2868 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2870 "fullname": "getValues",
2871 "filename": "${hhi_path}/BuiltinEnum.hhi",
2874 "base_class": "\\Elsa",
2878 "label": "getNames",
2880 "detail": "function(): dict<Elsa, string>",
2881 "inlineDetail": "()",
2882 "itemType": "dict<Elsa, string>",
2883 "sortText": "getNames",
2884 "insertText": "getNames()",
2885 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2887 "fullname": "getNames",
2888 "filename": "${hhi_path}/BuiltinEnum.hhi",
2891 "base_class": "\\Elsa",
2897 "detail": "function(mixed $value): ?Elsa",
2898 "inlineDetail": "(mixed $value)",
2899 "itemType": "?Elsa",
2900 "sortText": "coerce",
2901 "insertText": "coerce(${1:\\$value})",
2902 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2904 "fullname": "coerce",
2905 "filename": "${hhi_path}/BuiltinEnum.hhi",
2908 "base_class": "\\Elsa",
2912 "label": "assertAll",
2914 "detail": "function(Traversable<mixed> $values): Container<Elsa>",
2915 "inlineDetail": "(Traversable<mixed> $values)",
2916 "itemType": "Container<Elsa>",
2917 "sortText": "assertAll",
2918 "insertText": "assertAll(${1:\\$values})",
2919 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2921 "fullname": "assertAll",
2922 "filename": "${hhi_path}/BuiltinEnum.hhi",
2925 "base_class": "\\Elsa",
2931 "detail": "function(mixed $value): Elsa",
2932 "inlineDetail": "(mixed $value)",
2934 "sortText": "assert",
2935 "insertText": "assert(${1:\\$value})",
2936 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
2938 "fullname": "assert",
2939 "filename": "${hhi_path}/BuiltinEnum.hhi",
2942 "base_class": "\\Elsa",
2947 powered_by
="serverless_ide",
2950 comment
="Add 'switch (Elsa::Alonso) { case Elsa::Alonso:'",
2951 method
="textDocument/didChange",
2953 "textDocument": {"uri": "${php_file_uri}"},
2957 "start": {"line": 3, "character": 0},
2958 "end": {"line": 3, "character": 35},
2960 "text": "switch (Elsa::Alonso) { case Elsa::Alonso:",
2967 comment
="autocomplete after 'switch (Elsa::Alonso) { case Elsa::Alonso:'",
2968 method
="textDocument/completion",
2970 "textDocument": {"uri": "${php_file_uri}"},
2971 "position": {"line": 3, "character": 42},
2973 result
={"isIncomplete": False, "items": []},
2974 powered_by
="serverless_ide",
2977 comment
="Add 'DeprecatedClass::'",
2978 method
="textDocument/didChange",
2980 "textDocument": {"uri": "${php_file_uri}"},
2984 "start": {"line": 3, "character": 0},
2985 "end": {"line": 3, "character": 41},
2987 "text": "DeprecatedClass::",
2994 comment
="autocomplete after 'DeprecatedClass::'",
2995 method
="textDocument/completion",
2997 "textDocument": {"uri": "${php_file_uri}"},
2998 "position": {"line": 3, "character": 17},
3001 "isIncomplete": False,
3006 "detail": "classname<this>",
3007 "inlineDetail": "classname<this>",
3008 "sortText": "class",
3009 "insertText": "class",
3010 "insertTextFormat": InsertTextFormat
.PlainText
.value
,
3012 "fullname": "class",
3013 "filename": "${root_path}/completion_extras.php",
3016 "base_class": "\\DeprecatedClass",
3020 "label": "test_do_not_use",
3022 "detail": "function(): void",
3023 "inlineDetail": "()",
3025 "sortText": "~test_do_not_use",
3026 "insertText": "test_do_not_use()",
3027 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3029 "fullname": "test_do_not_use",
3030 "filename": "${root_path}/completion_extras.php",
3033 "base_class": "\\DeprecatedClass",
3039 "detail": "function(): void",
3040 "inlineDetail": "()",
3042 "sortText": "getName",
3043 "insertText": "getName()",
3044 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3046 "fullname": "getName",
3047 "filename": "${root_path}/completion_extras.php",
3050 "base_class": "\\DeprecatedClass",
3054 "label": "getAttributes_DO_NOT_USE",
3056 "detail": "function(): void",
3057 "inlineDetail": "()",
3059 "sortText": "~getAttributes_DO_NOT_USE",
3060 "insertText": "getAttributes_DO_NOT_USE()",
3061 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3063 "fullname": "getAttributes_DO_NOT_USE",
3064 "filename": "${root_path}/completion_extras.php",
3067 "base_class": "\\DeprecatedClass",
3071 "label": "__getLoader",
3073 "detail": "function(): void",
3074 "inlineDetail": "()",
3076 "sortText": "~__getLoader",
3077 "insertText": "__getLoader()",
3078 "insertTextFormat": InsertTextFormat
.Snippet
.value
,
3080 "fullname": "__getLoader",
3081 "filename": "${root_path}/completion_extras.php",
3084 "base_class": "\\DeprecatedClass",
3089 powered_by
="serverless_ide",
3091 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3092 .notification(method
="exit", params
={})
3094 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3096 def test_serverless_ide_definition(self
) -> None:
3097 variables
= dict(self
.prepare_serverless_ide_environment())
3098 variables
.update(self
.setup_php_file("definition.php"))
3099 self
.test_driver
.stop_hh_server()
3102 self
.initialize_spec(
3103 LspTestSpec("serverless_ide_definition"), use_serverless_ide
=True
3106 method
="textDocument/didOpen",
3109 "uri": "${php_file_uri}",
3110 "languageId": "hack",
3112 "text": "${php_file}",
3118 comment
="call to `b_definition`",
3119 method
="textDocument/definition",
3121 "textDocument": {"uri": "${php_file_uri}"},
3122 "position": {"line": 3, "character": 10},
3126 "uri": "file://${root_path}/definition.php",
3128 "start": {"line": 6, "character": 9},
3129 "end": {"line": 6, "character": 21},
3131 "title": "b_definition",
3134 powered_by
="serverless_ide",
3138 comment
="call to `new BB(1)`",
3139 method
="textDocument/definition",
3141 "textDocument": {"uri": "${php_file_uri}"},
3142 "position": {"line": 29, "character": 13},
3146 "uri": "file://${root_path}/definition.php",
3148 "start": {"line": 11, "character": 18},
3149 "end": {"line": 11, "character": 29},
3151 "title": "BB::__construct",
3154 powered_by
="serverless_ide",
3158 comment
="call to `new CC(1)`",
3159 method
="textDocument/definition",
3161 "textDocument": {"uri": "${php_file_uri}"},
3162 "position": {"line": 30, "character": 13},
3166 "uri": "file://${root_path}/definition.php",
3168 "start": {"line": 14, "character": 6},
3169 "end": {"line": 14, "character": 8},
3174 "uri": "file://${root_path}/definition.php",
3176 "start": {"line": 11, "character": 18},
3177 "end": {"line": 11, "character": 29},
3179 "title": "BB::__construct",
3182 powered_by
="serverless_ide",
3186 comment
="call to `new DD(1)`",
3187 method
="textDocument/definition",
3189 "textDocument": {"uri": "${php_file_uri}"},
3190 "position": {"line": 31, "character": 13},
3194 "uri": "file://${root_path}/definition.php",
3196 "start": {"line": 17, "character": 6},
3197 "end": {"line": 17, "character": 8},
3202 "uri": "file://${root_path}/definition.php",
3204 "start": {"line": 11, "character": 18},
3205 "end": {"line": 11, "character": 29},
3207 "title": "BB::__construct",
3210 powered_by
="serverless_ide",
3214 comment
="call to `new EE(1)`",
3215 method
="textDocument/definition",
3217 "textDocument": {"uri": "${php_file_uri}"},
3218 "position": {"line": 32, "character": 13},
3222 "uri": "file://${root_path}/definition.php",
3224 "start": {"line": 21, "character": 18},
3225 "end": {"line": 21, "character": 29},
3227 "title": "EE::__construct",
3230 powered_by
="serverless_ide",
3234 comment
="call to `new FF(1)`",
3235 method
="textDocument/definition",
3237 "textDocument": {"uri": "${php_file_uri}"},
3238 "position": {"line": 33, "character": 13},
3242 "uri": "file://${root_path}/definition.php",
3244 "start": {"line": 26, "character": 6},
3245 "end": {"line": 26, "character": 8},
3250 powered_by
="serverless_ide",
3254 comment
="call to `new TakesString(HasString::MyString)`",
3255 method
="textDocument/definition",
3257 "textDocument": {"uri": "${php_file_uri}"},
3258 "position": {"line": 45, "character": 23},
3262 "uri": "file://${root_path}/definition.php",
3264 "start": {"line": 40, "character": 6},
3265 "end": {"line": 40, "character": 15},
3267 "title": "HasString",
3270 powered_by
="serverless_ide",
3273 comment
="make local, unsaved change to the file",
3274 method
="textDocument/didChange",
3276 "textDocument": {"uri": "${php_file_uri}", "version": 2},
3281 "start": {"line": 3, "character": 9},
3282 "end": {"line": 3, "character": 21},
3290 comment
="call to `test` instead of `b_definition`",
3291 method
="textDocument/definition",
3293 "textDocument": {"uri": "${php_file_uri}"},
3294 "position": {"line": 3, "character": 10},
3298 "uri": "file://${root_path}/definition.php",
3300 "start": {"line": 28, "character": 9},
3301 "end": {"line": 28, "character": 13},
3306 powered_by
="serverless_ide",
3308 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3309 .notification(method
="exit", params
={})
3311 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3313 def test_serverless_ide_overridden_definition(self
) -> None:
3314 variables
= dict(self
.prepare_serverless_ide_environment())
3315 variables
.update(self
.setup_php_file("override.php"))
3316 self
.test_driver
.stop_hh_server()
3319 self
.initialize_spec(
3320 LspTestSpec("serverless_ide_overridden_definition"),
3321 use_serverless_ide
=True,
3324 method
="textDocument/didOpen",
3327 "uri": "${php_file_uri}",
3328 "languageId": "hack",
3330 "text": "${php_file}",
3336 comment
="find overridden method from trait. It's arbitrary which one we pick. This test embodies current (alphabetical) implementation.",
3337 method
="textDocument/definition",
3339 "textDocument": {"uri": "${php_file_uri}"},
3340 "position": {"line": 13, "character": 5},
3344 "uri": "file://${root_path}/override.php",
3346 "start": {"line": 7, "character": 18},
3347 "end": {"line": 7, "character": 21},
3349 "title": "MyTrait::foo",
3352 powered_by
="serverless_ide",
3356 comment
="find overridden static method. It's arbitrary which one we pick. This test embodies current (alphabetical) implementation.",
3357 method
="textDocument/definition",
3359 "textDocument": {"uri": "${php_file_uri}"},
3360 "position": {"line": 26, "character": 5},
3364 "uri": "file://${root_path}/override.php",
3366 "start": {"line": 23, "character": 25},
3367 "end": {"line": 23, "character": 28},
3372 powered_by
="serverless_ide",
3376 comment
="find overridden interface method",
3377 method
="textDocument/definition",
3379 "textDocument": {"uri": "${php_file_uri}"},
3380 "position": {"line": 35, "character": 5},
3384 "uri": "file://${root_path}/override.php",
3386 "start": {"line": 32, "character": 18},
3387 "end": {"line": 32, "character": 22},
3389 "title": "I1::quux",
3392 powered_by
="serverless_ide",
3394 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3395 .notification(method
="exit", params
={})
3397 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3399 def test_serverless_ide_document_symbol(self
) -> None:
3400 variables
= dict(self
.prepare_serverless_ide_environment())
3401 variables
.update(self
.setup_php_file("definition.php"))
3402 self
.test_driver
.stop_hh_server()
3405 self
.initialize_spec(
3406 LspTestSpec("serverless_ide_document_symbol"), use_serverless_ide
=True
3409 method
="textDocument/didOpen",
3412 "uri": "${php_file_uri}",
3413 "languageId": "hack",
3415 "text": "${php_file}",
3421 comment
="documentSymbol call",
3422 method
="textDocument/documentSymbol",
3423 params
={"textDocument": {"uri": "${php_file_uri}"}},
3429 "uri": "file://${root_path}/definition.php",
3431 "start": {"line": 50, "character": 18},
3432 "end": {"line": 50, "character": 47},
3435 "containerName": "MyEnumClass",
3438 "name": "MyEnumClass",
3441 "uri": "file://${root_path}/definition.php",
3443 "start": {"line": 49, "character": 0},
3444 "end": {"line": 52, "character": 1},
3449 "name": "testClassMemberInsideConstructorInvocation",
3452 "uri": "file://${root_path}/definition.php",
3454 "start": {"line": 44, "character": 0},
3455 "end": {"line": 46, "character": 1},
3463 "uri": "file://${root_path}/definition.php",
3465 "start": {"line": 41, "character": 8},
3466 "end": {"line": 41, "character": 29},
3469 "containerName": "HasString",
3472 "name": "HasString",
3475 "uri": "file://${root_path}/definition.php",
3477 "start": {"line": 40, "character": 0},
3478 "end": {"line": 42, "character": 1},
3483 "name": "__construct",
3486 "uri": "file://${root_path}/definition.php",
3488 "start": {"line": 37, "character": 2},
3489 "end": {"line": 37, "character": 43},
3492 "containerName": "TakesString",
3495 "name": "TakesString",
3498 "uri": "file://${root_path}/definition.php",
3500 "start": {"line": 36, "character": 0},
3501 "end": {"line": 38, "character": 1},
3509 "uri": "file://${root_path}/definition.php",
3511 "start": {"line": 26, "character": 0},
3512 "end": {"line": 26, "character": 11},
3517 "name": "__construct",
3520 "uri": "file://${root_path}/definition.php",
3522 "start": {"line": 21, "character": 2},
3523 "end": {"line": 23, "character": 3},
3526 "containerName": "EE",
3532 "uri": "file://${root_path}/definition.php",
3534 "start": {"line": 20, "character": 0},
3535 "end": {"line": 24, "character": 1},
3543 "uri": "file://${root_path}/definition.php",
3545 "start": {"line": 14, "character": 0},
3546 "end": {"line": 15, "character": 1},
3551 "name": "__construct",
3554 "uri": "file://${root_path}/definition.php",
3556 "start": {"line": 11, "character": 2},
3557 "end": {"line": 11, "character": 40},
3560 "containerName": "BB",
3566 "uri": "file://${root_path}/definition.php",
3568 "start": {"line": 10, "character": 0},
3569 "end": {"line": 12, "character": 1},
3574 "name": "a_definition",
3577 "uri": "file://${root_path}/definition.php",
3579 "start": {"line": 2, "character": 0},
3580 "end": {"line": 4, "character": 1},
3585 "name": "b_definition",
3588 "uri": "file://${root_path}/definition.php",
3590 "start": {"line": 6, "character": 0},
3591 "end": {"line": 8, "character": 1},
3599 "uri": "file://${root_path}/definition.php",
3601 "start": {"line": 17, "character": 0},
3602 "end": {"line": 18, "character": 1},
3610 "uri": "file://${root_path}/definition.php",
3612 "start": {"line": 28, "character": 0},
3613 "end": {"line": 34, "character": 1},
3618 "name": "MyEnumClassKind",
3621 "uri": "file://${root_path}/definition.php",
3623 "start": {"line": 48, "character": 0},
3624 "end": {"line": 48, "character": 24},
3632 "uri": "${php_file_uri}",
3634 "start": {"line": 51, "character": 18},
3635 "end": {"line": 51, "character": 48},
3638 "containerName": "MyEnumClass",
3641 powered_by
="serverless_ide",
3643 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3644 .notification(method
="exit", params
={})
3646 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3648 def initialize_spec(
3651 use_serverless_ide
: bool,
3652 supports_status
: bool = False, # does the caller wish to see all status messages?
3653 supports_init
: bool = False, # do we wish to interact with init, rather than waiting for init ok?
3655 if use_serverless_ide
:
3656 initialization_options
= {
3657 "namingTableSavedStatePath": "${naming_table_saved_state_path}",
3658 "namingTableSavedStateTestDelay": 0.0,
3661 # A small delay, since otherwise init completes immediately
3662 # This isn't very racy. All we need is a tiny delay so that
3663 # other things which are in the queue get processed, rather
3664 # than continuing synchronously
3665 initialization_options
["namingTableSavedStateTestDelay"] = 0.5
3667 initialization_options
= {}
3669 window_capabilities
= {}
3671 window_capabilities
["status"] = {"dynamicRegistration": False}
3673 spec
= spec
.ignore_notifications(method
="telemetry/event").request(
3675 method
="initialize",
3677 "initializationOptions": initialization_options
,
3679 "rootPath": "${root_path}",
3681 "window": window_capabilities
,
3683 "completion": {"completionItem": {"snippetSupport": True}}
3689 "textDocumentSync": {
3693 "willSaveWaitUntil": True,
3694 "save": {"includeText": False},
3696 "hoverProvider": True,
3697 "completionProvider": {
3698 "resolveProvider": True,
3699 "triggerCharacters": [
3712 "signatureHelpProvider": {"triggerCharacters": ["(", ","]},
3713 "definitionProvider": True,
3714 "typeDefinitionProvider": True,
3715 "referencesProvider": True,
3716 "documentHighlightProvider": True,
3717 "documentSymbolProvider": True,
3718 "workspaceSymbolProvider": True,
3719 "codeActionProvider": True,
3720 "documentFormattingProvider": True,
3721 "documentRangeFormattingProvider": True,
3722 "documentOnTypeFormattingProvider": {
3723 "firstTriggerCharacter": ";",
3724 "moreTriggerCharacter": ["}"],
3726 "renameProvider": True,
3727 "implementationProvider": True,
3728 "typeCoverageProvider": True,
3729 "rageProvider": True,
3733 if use_serverless_ide
:
3734 spec
= spec
.wait_for_server_request(
3735 method
="client/registerCapability",
3739 "id": "did-change-watched-files",
3740 "method": "workspace/didChangeWatchedFiles",
3741 "registerOptions": {
3744 "globPattern": "**/*.{php,phpt,hack,hackpartial,hck,hh,hhi,xhp}",
3754 if not supports_status
:
3755 spec
= spec
.ignore_status_diagnostics(True)
3757 if use_serverless_ide
and not supports_init
:
3758 spec
= spec
.wait_for_notification(
3759 comment
="wait for sIDE to finish init",
3760 method
="telemetry/event",
3761 params
={"type": 4, "message": "[client-ide] Finished init: ok"},
3766 def test_serverless_ide_type_definition(self
) -> None:
3767 variables
= dict(self
.prepare_serverless_ide_environment())
3768 variables
.update(self
.setup_php_file("type_definition.php"))
3769 self
.test_driver
.stop_hh_server()
3772 self
.initialize_spec(
3773 LspTestSpec("serverless_ide_type_definition"), use_serverless_ide
=True
3776 method
="textDocument/didOpen",
3779 "uri": "${php_file_uri}",
3780 "languageId": "hack",
3782 "text": "${php_file}",
3788 comment
="Conditional Type Definition of HH or II",
3789 method
="textDocument/typeDefinition",
3791 "textDocument": {"uri": "${php_file_uri}"},
3792 "position": {"line": 32, "character": 2},
3796 "uri": "${php_file_uri}",
3798 "start": {"line": 2, "character": 6},
3799 "end": {"line": 2, "character": 8},
3804 "uri": "${php_file_uri}",
3806 "start": {"line": 12, "character": 6},
3807 "end": {"line": 12, "character": 8},
3812 powered_by
="serverless_ide",
3816 comment
="Standard Class Definition",
3817 method
="textDocument/typeDefinition",
3819 "textDocument": {"uri": "${php_file_uri}"},
3820 "position": {"line": 40, "character": 2},
3824 "uri": "${php_file_uri}",
3826 "start": {"line": 2, "character": 6},
3827 "end": {"line": 2, "character": 8},
3832 powered_by
="serverless_ide",
3836 comment
="Class Type Definition with Casting",
3837 method
="textDocument/typeDefinition",
3839 "textDocument": {"uri": "${php_file_uri}"},
3840 "position": {"line": 41, "character": 2},
3844 "uri": "${php_file_uri}",
3846 "start": {"line": 2, "character": 6},
3847 "end": {"line": 2, "character": 8},
3852 powered_by
="serverless_ide",
3856 comment
="Primitive Type Definition",
3857 method
="textDocument/typeDefinition",
3859 "textDocument": {"uri": "${php_file_uri}"},
3860 "position": {"line": 42, "character": 2},
3863 powered_by
="serverless_ide",
3867 comment
="Function Return Type Definition",
3868 method
="textDocument/typeDefinition",
3870 "textDocument": {"uri": "${php_file_uri}"},
3871 "position": {"line": 43, "character": 2},
3875 "uri": "${php_file_uri}",
3877 "start": {"line": 12, "character": 6},
3878 "end": {"line": 12, "character": 8},
3883 powered_by
="serverless_ide",
3887 comment
="Function definition with primitive return type",
3888 method
="textDocument/typeDefinition",
3890 "textDocument": {"uri": "${php_file_uri}"},
3891 "position": {"line": 44, "character": 2},
3895 "uri": "${php_file_uri}",
3897 "start": {"line": 22, "character": 9},
3898 "end": {"line": 22, "character": 29},
3900 "title": "(function(): int)",
3903 powered_by
="serverless_ide",
3905 .request(line
=line(), method
="shutdown", params
={}, result
=None)
3906 .notification(method
="exit", params
={})
3908 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
3910 def test_serverless_ide_hover(self
) -> None:
3911 variables
= dict(self
.prepare_serverless_ide_environment())
3912 variables
.update(self
.setup_php_file("hover.php"))
3913 self
.test_driver
.stop_hh_server()
3916 self
.initialize_spec(
3917 LspTestSpec("serverless_ide_hover"), use_serverless_ide
=True
3920 method
="textDocument/didOpen",
3923 "uri": "${php_file_uri}",
3924 "languageId": "hack",
3926 "text": "${php_file}",
3932 comment
="hover over function invocation",
3933 method
="textDocument/hover",
3935 "textDocument": {"uri": "${php_file_uri}"},
3936 "position": {"line": 3, "character": 16},
3940 {"language": "hack", "value": "int"},
3941 "A comment describing b_hover.",
3944 "start": {"line": 3, "character": 9},
3945 "end": {"line": 3, "character": 16},
3948 powered_by
="serverless_ide",
3952 comment
="hover over string literal outside call",
3953 method
="textDocument/hover",
3955 "textDocument": {"uri": "${php_file_uri}"},
3956 "position": {"line": 25, "character": 12}, # 9 - 16
3958 result
={"contents": [{"language": "hack", "value": "string"}]},
3959 powered_by
="serverless_ide",
3963 comment
="hover over string literal inside call",
3964 method
="textDocument/hover",
3966 "textDocument": {"uri": "${php_file_uri}"},
3967 "position": {"line": 26, "character": 20}, # 16 - 29
3969 result
={"contents": [{"language": "hack", "value": "string"}]},
3970 powered_by
="serverless_ide",
3974 comment
="hover over int literal inside call",
3975 method
="textDocument/hover",
3977 "textDocument": {"uri": "${php_file_uri}"},
3978 "position": {"line": 26, "character": 32}, # 31 - 33
3980 result
={"contents": [{"language": "hack", "value": "int"}]},
3981 powered_by
="serverless_ide",
3985 comment
="hover over constant reference",
3986 method
="textDocument/hover",
3988 "textDocument": {"uri": "${php_file_uri}"},
3989 "position": {"line": 15, "character": 19},
3993 {"language": "hack", "value": "THE_ANSWER"},
3994 "A comment describing THE_ANSWER",
3995 "int THE_ANSWER = 42",
3998 "start": {"line": 15, "character": 9},
3999 "end": {"line": 15, "character": 19},
4002 powered_by
="serverless_ide",
4006 comment
="hover over whitespace",
4007 method
="textDocument/hover",
4009 "textDocument": {"uri": "${php_file_uri}"},
4010 "position": {"line": 3, "character": 1},
4013 powered_by
="serverless_ide",
4017 comment
="hover over a keyword",
4018 method
="textDocument/hover",
4020 "textDocument": {"uri": "${php_file_uri}"},
4021 "position": {"line": 2, "character": 1},
4024 powered_by
="serverless_ide",
4028 comment
="hover over a comment",
4029 method
="textDocument/hover",
4031 "textDocument": {"uri": "${php_file_uri}"},
4032 "position": {"line": 1, "character": 4},
4035 powered_by
="serverless_ide",
4039 comment
="hover past the end of a line",
4040 method
="textDocument/hover",
4042 "textDocument": {"uri": "${php_file_uri}"},
4043 "position": {"line": 3, "character": 100},
4046 powered_by
="serverless_ide",
4050 comment
="hover past the end of a file",
4051 method
="textDocument/hover",
4053 "textDocument": {"uri": "${php_file_uri}"},
4054 "position": {"line": 300, "character": 0},
4057 powered_by
="serverless_ide",
4061 comment
="hover over class with copyright docblock",
4062 method
="textDocument/hover",
4064 "textDocument": {"uri": "${php_file_uri}"},
4065 "position": {"line": 37, "character": 15},
4069 {"language": "hack", "value": "final class CopyrightClass"},
4070 "Testing copyright removal",
4073 "start": {"line": 37, "character": 2},
4074 "end": {"line": 37, "character": 16},
4077 powered_by
="serverless_ide",
4081 comment
="hover over class with generated docblock",
4082 method
="textDocument/hover",
4084 "textDocument": {"uri": "${php_file_uri}"},
4085 "position": {"line": 58, "character": 15},
4089 {"language": "hack", "value": "final class GeneratedClass"},
4090 "Testing generated text removal",
4093 "start": {"line": 58, "character": 2},
4094 "end": {"line": 58, "character": 16},
4097 powered_by
="serverless_ide",
4101 comment
="hover over an primitive attribute in an xhp literal",
4102 method
="textDocument/hover",
4104 "textDocument": {"uri": "${php_file_uri}"},
4105 "position": {"line": 62, "character": 25},
4109 {"language": "hack", "value": "public ?string name"},
4110 ":xhp:enum-attribute::name docblock",
4113 "start": {"line": 62, "character": 22},
4114 "end": {"line": 62, "character": 26},
4117 powered_by
="serverless_ide",
4121 comment
="hover over a nonprimitive attribute in an xhp literal",
4122 method
="textDocument/hover",
4124 "textDocument": {"uri": "${php_file_uri}"},
4125 "position": {"line": 62, "character": 36},
4129 {"language": "hack", "value": "public ?MyEnum enum-attribute"}
4132 "start": {"line": 62, "character": 33},
4133 "end": {"line": 62, "character": 47},
4136 powered_by
="serverless_ide",
4140 comment
="hover over a generic attribute in an xhp literal",
4141 method
="textDocument/hover",
4143 "textDocument": {"uri": "${php_file_uri}"},
4144 "position": {"line": 63, "character": 16},
4148 {"language": "hack", "value": "public ?ID<EntSomething> id"}
4151 "start": {"line": 63, "character": 15},
4152 "end": {"line": 63, "character": 17},
4155 powered_by
="serverless_ide",
4158 comment
="Add '<xhp:enum-attribute name' to test hover for incomplete xhp attribute",
4159 method
="textDocument/didChange",
4161 "textDocument": {"uri": "${php_file_uri}"},
4165 "start": {"line": 69, "character": 0},
4166 "end": {"line": 69, "character": 0},
4168 "text": "<xhp:enum-attribute name",
4175 comment
="hover over an attribute in an xhp literal without a value",
4176 method
="textDocument/hover",
4178 "textDocument": {"uri": "${php_file_uri}"},
4179 "position": {"line": 69, "character": 22},
4183 {"language": "hack", "value": "public ?string name"},
4184 ":xhp:enum-attribute::name docblock",
4187 "start": {"line": 69, "character": 20},
4188 "end": {"line": 69, "character": 24},
4191 powered_by
="serverless_ide",
4193 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4194 .notification(method
="exit", params
={})
4196 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4198 def test_serverless_ide_file_touched_on_disk(self
) -> None:
4199 variables
= dict(self
.prepare_serverless_ide_environment())
4200 variables
.update(self
.setup_php_file("hover.php"))
4201 self
.test_driver
.stop_hh_server()
4204 self
.initialize_spec(
4205 LspTestSpec("serverless_ide_file_on_disk_change"),
4206 use_serverless_ide
=True,
4209 method
="textDocument/didOpen",
4212 "uri": "${php_file_uri}",
4213 "languageId": "hack",
4215 "text": "${php_file}",
4220 method
="workspace/didChangeWatchedFiles",
4221 params
={"changes": [{"uri": "${php_file_uri}", "type": 2}]},
4223 .wait_for_notification(
4224 comment
="wait for sIDE to process file change",
4225 method
="telemetry/event",
4228 "message": "[client-ide] Done processing file changes",
4233 method
="textDocument/hover",
4235 "textDocument": {"uri": "${php_file_uri}"},
4236 "position": {"line": 3, "character": 16},
4240 {"language": "hack", "value": "int"},
4241 "A comment describing b_hover.",
4244 "start": {"line": 3, "character": 9},
4245 "end": {"line": 3, "character": 16},
4248 powered_by
="serverless_ide",
4250 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4251 .notification(method
="exit", params
={})
4253 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4255 def test_serverless_ide_file_hover_with_errors(self
) -> None:
4256 variables
= dict(self
.prepare_serverless_ide_environment())
4257 variables
.update(self
.setup_php_file("hover_with_errors.php"))
4258 self
.test_driver
.stop_hh_server()
4261 self
.initialize_spec(
4262 LspTestSpec("serverless_ide_hover_with_errors"), use_serverless_ide
=True
4265 method
="textDocument/didOpen",
4268 "uri": "${php_file_uri}",
4269 "languageId": "hack",
4271 "text": "${php_file}",
4276 method
="workspace/didChangeWatchedFiles",
4277 params
={"changes": [{"uri": "${php_file_uri}", "type": 2}]},
4279 .wait_for_notification(
4280 comment
="wait for sIDE to process file change",
4281 method
="telemetry/event",
4284 "message": "[client-ide] Done processing file changes",
4289 comment
="Totally normal hover",
4290 method
="textDocument/hover",
4292 "textDocument": {"uri": "${php_file_uri}"},
4293 "position": {"line": 14, "character": 37},
4299 "value": "public static function staticMethod(string $z): void",
4301 'During testing, we\'ll remove the "public" tag from this '
4303 "to ensure that we can still get IDE services",
4304 "Full name: `HoverWithErrorsClass::staticMethod`",
4307 "end": {"character": 39, "line": 14},
4308 "start": {"character": 27, "line": 14},
4311 powered_by
="serverless_ide",
4314 comment
="Remove the 'public' visibility modifier which triggers AST->AAST errors",
4315 method
="textDocument/didChange",
4317 "textDocument": {"uri": "${php_file_uri}"},
4321 "start": {"line": 10, "character": 2},
4322 "end": {"line": 10, "character": 8},
4331 comment
="Hover should still work even if visibility modifier has been removed",
4332 method
="textDocument/hover",
4334 "textDocument": {"uri": "${php_file_uri}"},
4335 "position": {"line": 14, "character": 37},
4341 "value": "public static function staticMethod(string $z): void",
4343 'During testing, we\'ll remove the "public" tag from this '
4345 "to ensure that we can still get IDE services",
4346 "Full name: `HoverWithErrorsClass::staticMethod`",
4349 "end": {"character": 39, "line": 14},
4350 "start": {"character": 27, "line": 14},
4353 powered_by
="serverless_ide",
4355 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4356 .notification(method
="exit", params
={})
4358 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4360 def test_serverless_ide_formatting(self
) -> None:
4361 # This test will fail if hackfmt can't be found
4362 if not self
.test_driver
.run_hackfmt_check():
4363 raise unittest
.SkipTest("Hackfmt can't be found. Skipping.")
4365 variables
= dict(self
.prepare_serverless_ide_environment())
4366 variables
.update(self
.setup_php_file("messy.php"))
4368 self
.test_driver
.stop_hh_server()
4371 self
.initialize_spec(LspTestSpec("formatting"), use_serverless_ide
=True)
4373 method
="textDocument/didOpen",
4376 "uri": "${php_file_uri}",
4377 "languageId": "hack",
4379 "text": "${php_file}",
4385 method
="textDocument/formatting",
4387 "textDocument": {"uri": "${php_file_uri}"},
4388 "options": {"tabSize": 5, "insertSpaces": True},
4393 "start": {"line": 0, "character": 0},
4394 "end": {"line": 12, "character": 0},
4396 "newText": "<?hh //strict\n\nfunction x(): string {\n"
4397 + ' $a = "this";\n\n'
4399 + ' $c = "messy";\n\n'
4401 + ' return "$a"."$b"."$c"."d";\n}\n',
4405 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4406 .notification(method
="exit", params
={})
4408 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4410 def test_serverless_ide_rangeformatting(self
) -> None:
4411 # This test will fail if hackfmt can't be found
4412 if not self
.test_driver
.run_hackfmt_check():
4413 raise unittest
.SkipTest("Hackfmt can't be found. Skipping.")
4415 variables
= dict(self
.prepare_serverless_ide_environment())
4416 variables
.update(self
.setup_php_file("messy.php"))
4418 self
.test_driver
.stop_hh_server()
4421 self
.initialize_spec(
4422 LspTestSpec("range_formatting"), use_serverless_ide
=True
4425 method
="textDocument/didOpen",
4428 "uri": "${php_file_uri}",
4429 "languageId": "hack",
4431 "text": "${php_file}",
4437 method
="textDocument/rangeFormatting",
4439 "textDocument": {"uri": "${php_file_uri}"},
4441 "start": {"line": 3, "character": 0},
4442 "end": {"line": 4, "character": 0},
4444 "options": {"tabSize": 5, "insertSpaces": True},
4449 "start": {"line": 3, "character": 0},
4450 "end": {"line": 4, "character": 0},
4452 "newText": ' $a = "this";\n',
4456 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4457 .notification(method
="exit", params
={})
4459 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4461 def test_serverless_ide_ontypeformatting(self
) -> None:
4462 # This test will fail if hackfmt can't be found
4463 if not self
.test_driver
.run_hackfmt_check():
4464 raise unittest
.SkipTest("Hackfmt can't be found. Skipping.")
4466 variables
= dict(self
.prepare_serverless_ide_environment())
4467 variables
.update(self
.setup_php_file("ontypeformatting.php"))
4470 self
.initialize_spec(
4471 LspTestSpec("ontypeformatting"), use_serverless_ide
=True
4474 method
="textDocument/didOpen",
4477 "uri": "${php_file_uri}",
4478 "languageId": "hack",
4480 "text": "${php_file}",
4486 method
="textDocument/onTypeFormatting",
4488 "textDocument": {"uri": "${php_file_uri}"},
4489 "position": {"line": 9, "character": 58},
4491 "options": {"tabSize": 2, "insertSpaces": True},
4496 "start": {"line": 5, "character": 23},
4497 "end": {"line": 9, "character": 58},
4499 "newText": "{\n test_otf(\n"
4500 + " '1234567890',\n"
4501 + " '1234567890',\n"
4502 + " '1234567890',\n"
4503 + " '1234567890',\n"
4504 + " '1234567890',\n"
4505 + " '1234567890',\n );",
4511 method
="textDocument/onTypeFormatting",
4513 "textDocument": {"uri": "${php_file_uri}"},
4514 "position": {"line": 15, "character": 23},
4516 "options": {"tabSize": 2, "insertSpaces": True},
4521 "start": {"line": 15, "character": 0},
4522 "end": {"line": 15, "character": 23},
4524 "newText": "function otf(): void {}",
4528 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4529 .notification(method
="exit", params
={})
4532 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
4534 def test_did_change(self
) -> None:
4535 self
.prepare_server_environment()
4536 variables
= self
.setup_php_file("didchange.php")
4538 self
.initialize_spec(LspTestSpec("did_change"), use_serverless_ide
=False)
4539 .wait_for_hh_server_ready()
4541 method
="textDocument/didOpen",
4544 "uri": "${php_file_uri}",
4545 "languageId": "hack",
4547 "text": "${php_file}",
4552 method
="textDocument/didChange",
4554 "textDocument": {"uri": "${php_file_uri}"},
4558 "start": {"line": 7, "character": 11},
4559 "end": {"line": 7, "character": 12},
4566 .wait_for_notification(
4567 method
="textDocument/publishDiagnostics",
4569 "uri": "${php_file_uri}",
4573 "start": {"line": 7, "character": 11},
4574 "end": {"line": 7, "character": 11},
4579 "message": "A semicolon ; is expected here.",
4580 "relatedLocations": [],
4581 "relatedInformation": [],
4586 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4587 .wait_for_notification(
4588 comment
="Hack appears to clear out diagnostics before shutting down",
4589 method
="textDocument/publishDiagnostics",
4590 params
={"uri": "${php_file_uri}", "diagnostics": []},
4592 .notification(method
="exit", params
={})
4594 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
4596 def test_go_to_implementation(self
) -> None:
4597 self
.prepare_server_environment()
4598 variables
= self
.setup_php_file("go_to_implementation.php")
4600 self
.initialize_spec(
4601 LspTestSpec("test_go_to_implementation"), use_serverless_ide
=False
4603 .wait_for_hh_server_ready()
4605 method
="textDocument/didOpen",
4608 "uri": "${php_file_uri}",
4609 "languageId": "hack",
4611 "text": "${php_file}",
4617 comment
="go to implemenetation: abstract class",
4618 method
="textDocument/implementation",
4620 "textDocument": {"uri": "${php_file_uri}"},
4621 "position": {"line": 1, "character": 17},
4625 "uri": "${php_file_uri}",
4627 "start": {"line": 7, "character": 6},
4628 "end": {"line": 7, "character": 9},
4635 comment
="go to implemenetation: interface",
4636 method
="textDocument/implementation",
4638 "textDocument": {"uri": "${php_file_uri}"},
4639 "position": {"line": 13, "character": 13},
4643 "uri": "${php_file_uri}",
4645 "start": {"line": 17, "character": 6},
4646 "end": {"line": 17, "character": 9},
4653 comment
="go to implemenetation: trait",
4654 method
="textDocument/implementation",
4656 "textDocument": {"uri": "${php_file_uri}"},
4657 "position": {"line": 23, "character": 10},
4661 "uri": "${php_file_uri}",
4663 "start": {"line": 30, "character": 6},
4664 "end": {"line": 30, "character": 16},
4671 comment
="go to implemenetation: method",
4672 method
="textDocument/implementation",
4674 "textDocument": {"uri": "${php_file_uri}"},
4675 "position": {"line": 19, "character": 18},
4679 "uri": "${php_file_uri}",
4681 "start": {"line": 8, "character": 18},
4682 "end": {"line": 8, "character": 22},
4687 .request(line
=line(), method
="shutdown", params
={}, result
=None)
4688 .notification(method
="exit", params
={})
4690 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
4692 def test_signature_help(self
) -> None:
4693 self
.prepare_server_environment()
4694 variables
= self
.setup_php_file("signaturehelp.php")
4696 self
.initialize_spec(
4697 LspTestSpec("test_signature_help"), use_serverless_ide
=False
4699 .wait_for_hh_server_ready()
4701 method
="textDocument/didOpen",
4704 "uri": "${php_file_uri}",
4705 "languageId": "hack",
4707 "text": "${php_file}",
4713 comment
="signature help for 0-argument constructor"
4714 " (left of opening paren)",
4715 method
="textDocument/signatureHelp",
4717 "textDocument": {"uri": "${php_file_uri}"},
4718 "position": {"line": 16, "character": 18},
4724 comment
="signature help for 0-argument constructor",
4725 method
="textDocument/signatureHelp",
4727 "textDocument": {"uri": "${php_file_uri}"},
4728 "position": {"line": 16, "character": 19},
4733 "label": "public function __construct(): void",
4734 "documentation": "Constructor with doc block",
4738 "activeSignature": 0,
4739 "activeParameter": 0,
4744 comment
="signature help for 0-argument constructor"
4745 " (right of closing paren)",
4746 method
="textDocument/signatureHelp",
4748 "textDocument": {"uri": "${php_file_uri}"},
4749 "position": {"line": 16, "character": 20},
4755 comment
="signature help for 2-argument instance method"
4756 " (left of opening paren)",
4757 method
="textDocument/signatureHelp",
4759 "textDocument": {"uri": "${php_file_uri}"},
4760 "position": {"line": 17, "character": 20},
4766 comment
="signature help for 2-argument instance method"
4767 " (right of opening paren)",
4768 method
="textDocument/signatureHelp",
4770 "textDocument": {"uri": "${php_file_uri}"},
4771 "position": {"line": 17, "character": 21},
4776 "label": "public function instanceMethod"
4777 "(int $x1, int $x2): void",
4778 "documentation": "Instance method with doc block",
4779 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4782 "activeSignature": 0,
4783 "activeParameter": 0,
4788 comment
="signature help for 2-argument instance method"
4789 " (left of first comma)",
4790 method
="textDocument/signatureHelp",
4792 "textDocument": {"uri": "${php_file_uri}"},
4793 "position": {"line": 17, "character": 22},
4798 "label": "public function instanceMethod"
4799 "(int $x1, int $x2): void",
4800 "documentation": "Instance method with doc block",
4801 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4804 "activeSignature": 0,
4805 "activeParameter": 1,
4810 comment
="signature help for 2-argument instance method"
4811 " (right of first comma)",
4812 method
="textDocument/signatureHelp",
4814 "textDocument": {"uri": "${php_file_uri}"},
4815 "position": {"line": 17, "character": 23},
4820 "label": "public function instanceMethod"
4821 "(int $x1, int $x2): void",
4822 "documentation": "Instance method with doc block",
4823 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4826 "activeSignature": 0,
4827 "activeParameter": 1,
4832 comment
="signature help for 2-argument instance method"
4833 " (left of closing paren)",
4834 method
="textDocument/signatureHelp",
4836 "textDocument": {"uri": "${php_file_uri}"},
4837 "position": {"line": 17, "character": 24},
4842 "label": "public function instanceMethod"
4843 "(int $x1, int $x2): void",
4844 "documentation": "Instance method with doc block",
4845 "parameters": [{"label": "$x1"}, {"label": "$x2"}],
4848 "activeSignature": 0,
4849 "activeParameter": 1,
4854 comment
="signature help for 2-argument instance method"
4855 " (right of closing paren)",
4856 method
="textDocument/signatureHelp",
4858 "textDocument": {"uri": "${php_file_uri}"},
4859 "position": {"line": 17, "character": 25},
4865 comment
="signature help for 1-argument static method"
4866 " (left of open paren)",
4867 method
="textDocument/signatureHelp",
4869 "textDocument": {"uri": "${php_file_uri}"},
4870 "position": {"line": 18, "character": 23},
4876 comment
="signature help for 1-argument static method"
4877 " (right of open paren)",
4878 method
="textDocument/signatureHelp",
4880 "textDocument": {"uri": "${php_file_uri}"},
4881 "position": {"line": 18, "character": 24},
4886 "label": "public static function staticMethod"
4887 "(string $z): void",
4888 "documentation": "Static method with doc block",
4889 "parameters": [{"label": "$z"}],
4892 "activeSignature": 0,
4893 "activeParameter": 0,
4898 comment
="signature help for 2-argument global function"
4899 " (left of open paren)",
4900 method
="textDocument/signatureHelp",
4902 "textDocument": {"uri": "${php_file_uri}"},
4903 "position": {"line": 19, "character": 17},
4909 comment
="signature help for 2-argument global function"
4910 " (right of open paren)",
4911 method
="textDocument/signatureHelp",
4913 "textDocument": {"uri": "${php_file_uri}"},
4914 "position": {"line": 19, "character": 18},
4919 "label": "function global_function"
4920 "(string $s, int $x): void",
4921 "documentation": "Global function with doc block",
4922 "parameters": [{"label": "$s"}, {"label": "$x"}],
4925 "activeSignature": 0,
4926 "activeParameter": 0,
4931 comment
="signature help for 1-argument namespace-aliased global"
4932 " function (right of open paren)",
4933 method
="textDocument/signatureHelp",
4935 "textDocument": {"uri": "${php_file_uri}"},
4936 "position": {"line": 20, "character": 26},
4942 comment
="signature help for 1-argument namespace-aliased global"
4943 " function (right of open paren)",
4944 method
="textDocument/signatureHelp",
4946 "textDocument": {"uri": "${php_file_uri}"},
4947 "position": {"line": 20, "character": 26},
4953 comment
="signature help for 1-argument namespace-aliased global"
4954 " function (right of open paren)",
4955 method
="textDocument/signatureHelp",
4957 "textDocument": {"uri": "${php_file_uri}"},
4958 "position": {"line": 20, "character": 27},
4963 "label": "function Derp\\Lib\\Herp\\aliased_global_func(string $s): void",
4964 "documentation": "Namespace-aliased function with doc block",
4965 "parameters": [{"label": "$s"}],
4968 "activeSignature": 0,
4969 "activeParameter": 0,
4974 comment
="signature help for 1-argument namespace-aliased global"
4975 " function (right of open paren)",
4976 method
="textDocument/signatureHelp",
4978 "textDocument": {"uri": "${php_file_uri}"},
4979 "position": {"line": 20, "character": 28},
4984 "label": "function Derp\\Lib\\Herp\\aliased_global_func(string $s): void",
4985 "documentation": "Namespace-aliased function with doc block",
4986 "parameters": [{"label": "$s"}],
4989 "activeSignature": 0,
4990 "activeParameter": 0,
4995 comment
="signature help for 2-argument function with params"
4996 " (right of open paren)",
4997 method
="textDocument/signatureHelp",
4999 "textDocument": {"uri": "${php_file_uri}"},
5000 "position": {"line": 21, "character": 30},
5005 "label": "function test_signature_help_params1("
5006 "\n string $param1,\n string $param2\n): void",
5007 "documentation": "comment describing the method"
5008 "\n@param $param1 info1"
5009 "\n@param param2 info2",
5011 {"label": "$param1", "documentation": "info1"},
5012 {"label": "$param2", "documentation": "info2"},
5016 "activeSignature": 0,
5017 "activeParameter": 0,
5022 comment
="signature help for 2-argument function with params"
5023 " (right of open paren)",
5024 method
="textDocument/signatureHelp",
5026 "textDocument": {"uri": "${php_file_uri}"},
5027 "position": {"line": 22, "character": 30},
5032 "label": "function test_signature_help_params2("
5033 "\n string $param1,\n string $param2\n): void",
5034 "documentation": "comment describing the method"
5035 "\n@param $param1 info1",
5037 {"label": "$param1", "documentation": "info1"},
5038 {"label": "$param2"},
5042 "activeSignature": 0,
5043 "activeParameter": 0,
5048 comment
="signature help for 2-argument function with params"
5049 " (right of open paren)",
5050 method
="textDocument/signatureHelp",
5052 "textDocument": {"uri": "${php_file_uri}"},
5053 "position": {"line": 23, "character": 30},
5058 "label": "function test_signature_help_params3("
5059 "\n string $param1,\n string $param2\n): string",
5060 "documentation": "@param $param1 info1"
5062 "\n@param $param2 info2"
5063 "\n@return the string"
5068 "documentation": "info1 for param1",
5070 {"label": "$param2", "documentation": "info2"},
5074 "activeSignature": 0,
5075 "activeParameter": 0,
5078 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5079 .notification(method
="exit", params
={})
5081 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
5083 def test_signature_help_lambda(self
) -> None:
5084 self
.prepare_server_environment()
5085 variables
= self
.setup_php_file("signaturehelp_lambda.php")
5087 self
.initialize_spec(
5088 LspTestSpec("test_serverless_ide_signature_help_lambda"),
5089 use_serverless_ide
=False,
5091 .wait_for_hh_server_ready()
5093 method
="textDocument/didOpen",
5096 "uri": "${php_file_uri}",
5097 "languageId": "hack",
5099 "text": "${php_file}",
5105 comment
="signature help for a normal function call",
5106 method
="textDocument/signatureHelp",
5108 "textDocument": {"uri": "${php_file_uri}"},
5109 "position": {"line": 8, "character": 29},
5112 "activeParameter": 0,
5113 "activeSignature": 0,
5116 "label": "function test_lambda_sighelp(\n"
5118 " (function(string): int) $f\n"
5120 "parameters": [{"label": "$str"}, {"label": "$f"}],
5127 comment
="signature help for normal function call within a lambda",
5128 method
="textDocument/signatureHelp",
5130 "textDocument": {"uri": "${php_file_uri}"},
5131 "position": {"line": 9, "character": 21},
5134 "activeParameter": 0,
5135 "activeSignature": 0,
5138 "label": "function normal_test_func(string $str): void",
5139 "parameters": [{"label": "$str"}],
5146 comment
="signature help for text within a lambda, left side of an open paren",
5147 method
="textDocument/signatureHelp",
5149 "textDocument": {"uri": "${php_file_uri}"},
5150 "position": {"line": 10, "character": 15},
5156 comment
="signature help for text within a lambda, right side of an open paren",
5157 method
="textDocument/signatureHelp",
5159 "textDocument": {"uri": "${php_file_uri}"},
5160 "position": {"line": 10, "character": 16},
5164 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5165 .notification(method
="exit", params
={})
5167 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
5169 def test_rename(self
) -> None:
5170 self
.prepare_server_environment()
5171 variables
= self
.setup_php_file("rename.php")
5172 self
.load_and_run("rename", variables
)
5174 def test_references(self
) -> None:
5175 self
.prepare_server_environment()
5176 variables
= self
.setup_php_file("references.php")
5177 self
.load_and_run("references", variables
)
5179 def test_non_existing_method(self
) -> None:
5180 self
.prepare_server_environment()
5181 variables
= self
.setup_php_file("nomethod.php")
5182 self
.load_and_run("nomethod", variables
)
5184 def test_bad_call(self
) -> None:
5185 self
.prepare_server_environment()
5186 variables
= self
.setup_php_file("bad_call.php")
5187 self
.load_and_run("bad_call", variables
)
5189 def test_code_action_missing_method(self
) -> None:
5190 variables
= dict(self
.prepare_serverless_ide_environment())
5191 variables
.update(self
.setup_php_file("code_action_missing_method.php"))
5192 self
.test_driver
.stop_hh_server()
5195 self
.initialize_spec(
5196 LspTestSpec("code_action_missing_method"), use_serverless_ide
=True
5199 method
="textDocument/didOpen",
5202 "uri": "${php_file_uri}",
5203 "languageId": "hack",
5205 "text": "${php_file}",
5210 comment
="make local, unsaved change to the file",
5211 method
="textDocument/didChange",
5213 "textDocument": {"uri": "${php_file_uri}", "version": 2},
5219 class ClassWithFooBar {
5220 public function foobar(): void {}
5223 function call_method(ClassWithFooBar $mc): void {
5233 comment
="get actions",
5234 method
="textDocument/codeAction",
5236 "textDocument": {"uri": "${php_file_uri}"},
5238 "start": {"line": 7, "character": 7},
5239 "end": {"line": 7, "character": 13},
5245 "start": {"line": 7, "character": 7},
5246 "end": {"line": 7, "character": 13},
5251 "message": "No instance method foobaz in ClassWithFooBar",
5252 "relatedInformation": [
5255 "uri": "${php_file_uri}",
5257 "start": {"line": 3, "character": 18},
5258 "end": {"line": 3, "character": 24},
5261 "message": "Did you mean foobar instead?",
5265 "uri": "${php_file_uri}",
5267 "start": {"line": 6, "character": 21},
5268 "end": {"line": 6, "character": 36},
5271 "message": "This is why I think it is an object of type ClassWithFooBar",
5275 "uri": "${php_file_uri}",
5277 "start": {"line": 2, "character": 6},
5278 "end": {"line": 2, "character": 21},
5281 "message": "Declaration of ClassWithFooBar is here",
5284 "relatedLocations": [
5287 "uri": "${php_file_uri}",
5289 "start": {"line": 3, "character": 18},
5290 "end": {"line": 3, "character": 24},
5293 "message": "Did you mean foobar instead?",
5297 "uri": "${php_file_uri}",
5299 "start": {"line": 6, "character": 21},
5300 "end": {"line": 6, "character": 36},
5303 "message": "This is why I think it is an object of type ClassWithFooBar",
5307 "uri": "${php_file_uri}",
5309 "start": {"line": 2, "character": 6},
5310 "end": {"line": 2, "character": 21},
5313 "message": "Declaration of ClassWithFooBar is here",
5322 "title": "Change to ->foobar",
5327 "${root_path}/code_action_missing_method.php": [
5330 "start": {"line": 7, "character": 7},
5331 "end": {"line": 7, "character": 13},
5333 "newText": "foobar",
5340 powered_by
="serverless_ide",
5342 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5343 .notification(method
="exit", params
={})
5345 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5347 def test_non_blocking(self
) -> None:
5348 self
.prepare_server_environment()
5349 variables
= self
.setup_php_file("non_blocking.php")
5350 self
.test_driver
.start_hh_loop_forever_assert_timeout()
5352 self
.initialize_spec(LspTestSpec("non_blocking"), use_serverless_ide
=False)
5353 .ignore_notifications(method
="textDocument/publishDiagnostics")
5354 .wait_for_hh_server_ready()
5357 method
="textDocument/definition",
5359 "textDocument": {"uri": "${php_file_uri}"},
5360 "position": {"line": 7, "character": 11},
5364 "uri": "file://${root_path}/non_blocking.php",
5366 "start": {"line": 2, "character": 9},
5367 "end": {"line": 2, "character": 32},
5369 "title": "non_blocking_definition",
5372 wait_id
="definition request",
5375 comment
="remove hh_loop_forever() invocation to break the infinite loop",
5376 method
="textDocument/didOpen",
5379 "uri": "${root_path}/__hh_loop_forever_foo.php",
5380 "languageId": "hack",
5385 function __hh_loop_forever_foo(): int {
5392 .wait_for_response(wait_id
="definition request")
5393 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5394 .notification(method
="exit", params
={})
5396 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
5398 def test_serverless_ide_hierarchy_file_change_on_disk(self
) -> None:
5399 variables
= dict(self
.prepare_serverless_ide_environment())
5400 variables
.update(self
.setup_php_file("incremental_derived.php"))
5401 changed_php_file_uri
= self
.repo_file("incremental_base.php")
5402 variables
.update({"changed_php_file_uri": changed_php_file_uri
})
5403 self
.test_driver
.stop_hh_server()
5406 self
.initialize_spec(
5407 LspTestSpec("serverless_ide_hierarchy_file_change_on_disk"),
5408 use_serverless_ide
=True,
5411 method
="textDocument/didOpen",
5414 "uri": "${php_file_uri}",
5415 "languageId": "hack",
5417 "text": "${php_file}",
5423 comment
="hover before change to class hierarchy should be `int`",
5424 method
="textDocument/hover",
5426 "textDocument": {"uri": "${php_file_uri}"},
5427 "position": {"line": 7, "character": 14},
5431 {"language": "hack", "value": "public function foo(): int"},
5432 "Full name: `BaseClassIncremental::foo`",
5435 "start": {"line": 7, "character": 12},
5436 "end": {"line": 7, "character": 15},
5439 powered_by
="serverless_ide",
5442 uri
=changed_php_file_uri
,
5445 class BaseClassIncremental {
5446 public function foo(): string { return ''; }
5453 comment
="hover after change to class hierarchy should be `string`",
5454 method
="textDocument/hover",
5456 "textDocument": {"uri": "${php_file_uri}"},
5457 "position": {"line": 7, "character": 14},
5461 {"language": "hack", "value": "public function foo(): string"},
5462 "Full name: `BaseClassIncremental::foo`",
5465 "start": {"line": 7, "character": 12},
5466 "end": {"line": 7, "character": 15},
5469 powered_by
="serverless_ide",
5471 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5472 .notification(method
="exit", params
={})
5475 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5477 def test_serverless_ide_decl_in_unsaved_buffer_changed(self
) -> None:
5478 variables
= dict(self
.prepare_serverless_ide_environment())
5479 variables
.update(self
.setup_php_file("hover.php"))
5480 self
.test_driver
.stop_hh_server()
5483 self
.initialize_spec(
5484 LspTestSpec("serverless_ide_decl_in_unsaved_buffer_changed"),
5485 use_serverless_ide
=True,
5488 method
="textDocument/didOpen",
5491 "uri": "${php_file_uri}",
5492 "languageId": "hack",
5494 "text": "${php_file}",
5500 comment
="hover over function invocation",
5501 method
="textDocument/hover",
5503 "textDocument": {"uri": "${php_file_uri}"},
5504 "position": {"line": 3, "character": 16},
5508 {"language": "hack", "value": "int"},
5509 "A comment describing b_hover.",
5512 "start": {"line": 3, "character": 9},
5513 "end": {"line": 3, "character": 16},
5516 powered_by
="serverless_ide",
5519 comment
="make local, unsaved change to the file",
5520 method
="textDocument/didChange",
5522 "textDocument": {"uri": "${php_file_uri}", "version": 2},
5528 function a_hover(): int {
5531 // A comment describing b_hover differently.
5532 function b_hover(): string {
5542 comment
="another hover over function invocation, should be string now",
5543 method
="textDocument/hover",
5545 "textDocument": {"uri": "${php_file_uri}"},
5546 "position": {"line": 3, "character": 16},
5550 {"language": "hack", "value": "string"},
5551 "A comment describing b_hover differently.",
5554 "start": {"line": 3, "character": 9},
5555 "end": {"line": 3, "character": 16},
5558 powered_by
="serverless_ide",
5560 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5561 .notification(method
="exit", params
={})
5564 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5566 def test_serverless_ide_decl_two_unsaved_buffers(self
) -> None:
5567 variables
= dict(self
.prepare_serverless_ide_environment())
5568 variables
.update(self
.setup_php_file("unsaved1.php"))
5569 variables
.update({"unsaved2_file_uri": self
.repo_file_uri("unsaved2.php")})
5570 self
.test_driver
.stop_hh_server()
5573 self
.initialize_spec(
5574 LspTestSpec("test_serverless_ide_decl_two_unsaved_buffers"),
5575 use_serverless_ide
=True,
5578 comment
="open 'unsaved1.php', since we'll be hovering in it",
5579 method
="textDocument/didOpen",
5582 "uri": "${php_file_uri}",
5583 "languageId": "hack",
5585 "text": "${php_file}",
5590 comment
="open 'unsaved2.php' with a bool-returning signature, different from disk",
5591 method
="textDocument/didOpen",
5594 "uri": "${unsaved2_file_uri}",
5595 "languageId": "hack",
5599 function unsaved_bar(): bool { return true; }
5606 comment
="hover 'unsaved1.php' is with respect to disk contents of 'unsaved2.php'",
5607 method
="textDocument/hover",
5609 "textDocument": {"uri": "${php_file_uri}"},
5610 "position": {"line": 1, "character": 39},
5614 {"language": "hack", "value": "function unsaved_bar(): int"},
5617 "start": {"line": 1, "character": 34},
5618 "end": {"line": 1, "character": 45},
5621 powered_by
="serverless_ide",
5624 comment
="change signature in 'unsaved2.php' to return string",
5625 method
="textDocument/didChange",
5627 "textDocument": {"uri": "${unsaved2_file_uri}", "version": 2},
5632 function unsaved_bar(): string { return "hello"; }
5640 comment
="this is a dummy hover in 'unsaved2.php' just to ensure its decl is cached",
5641 method
="textDocument/hover",
5643 "textDocument": {"uri": "${unsaved2_file_uri}"},
5644 "position": {"line": 0, "character": 0},
5647 powered_by
="serverless_ide",
5651 comment
="hover 'unsaved1.php' is still with respect to disk contents of 'unsaved2.php'",
5652 method
="textDocument/hover",
5654 "textDocument": {"uri": "${php_file_uri}"},
5655 "position": {"line": 1, "character": 39},
5659 {"language": "hack", "value": "function unsaved_bar(): int"},
5662 "start": {"line": 1, "character": 34},
5663 "end": {"line": 1, "character": 45},
5666 powered_by
="serverless_ide",
5669 comment
="save signature in 'unsaved2' to return string",
5670 uri
=variables
["unsaved2_file_uri"],
5673 function unsaved_bar(): string { return "hello"; }
5679 comment
="hover 'unsaved1.php' gets new disk contents of 'unsaved2.php'",
5680 method
="textDocument/hover",
5682 "textDocument": {"uri": "${php_file_uri}"},
5683 "position": {"line": 1, "character": 39},
5687 {"language": "hack", "value": "function unsaved_bar(): string"},
5690 "start": {"line": 1, "character": 34},
5691 "end": {"line": 1, "character": 45},
5694 powered_by
="serverless_ide",
5696 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5697 .notification(method
="exit", params
={})
5700 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5702 def test_hover_without_file_open(self
) -> None:
5703 variables
= dict(self
.prepare_serverless_ide_environment())
5704 variables
.update(self
.setup_php_file("hover.php"))
5705 self
.test_driver
.stop_hh_server()
5708 self
.initialize_spec(
5709 LspTestSpec("test_hover_without_file_open"),
5710 use_serverless_ide
=True,
5711 supports_status
=True,
5713 .ignore_notifications(method
="textDocument/publishDiagnostics")
5715 comment
="Ignore 'initializing...' messages since they're racy",
5716 method
="window/showStatus",
5719 "actions": [{"title": "Restart hh_server"}],
5720 "message": "Hack IDE: initializing.\nhh_server: stopped.",
5721 "shortMessage": "Hack: initializing",
5725 comment
="another racy initializing, before hh_server has even responded",
5726 method
="window/showStatus",
5730 "message": "Hack IDE: initializing.",
5731 "shortMessage": "Hack: initializing",
5735 comment
="another racy initialization to ignore, again before hh_server",
5736 method
="window/showStatus",
5740 "message": "Hack IDE: ready.",
5741 "shortMessage": "Hack: ready",
5744 .wait_for_server_request(
5745 method
="window/showStatus",
5747 "actions": [{"title": "Restart hh_server"}],
5748 "message": "Hack IDE: ready.\nhh_server: stopped.",
5749 "shortMessage": "Hack: ready",
5752 result
=NoResponse(),
5756 comment
="hover before file_open will fail",
5757 method
="textDocument/hover",
5759 "textDocument": {"uri": "${php_file_uri}"},
5760 "position": {"line": 26, "character": 20},
5765 method
="textDocument/didOpen",
5768 "uri": "${php_file_uri}",
5769 "languageId": "hack",
5771 "text": "${php_file}",
5777 comment
="hover after file_open will succeed",
5778 method
="textDocument/hover",
5780 "textDocument": {"uri": "${php_file_uri}"},
5781 "position": {"line": 26, "character": 20},
5783 result
={"contents": [{"language": "hack", "value": "string"}]},
5784 powered_by
="serverless_ide",
5788 method
="$test/shutdownServerlessIde",
5791 powered_by
="serverless_ide",
5793 .wait_for_server_request(
5794 method
="window/showStatus",
5797 {"title": "Restart Hack IDE"},
5798 {"title": "Restart hh_server"},
5800 "message": "Hack IDE has failed. See Output›Hack for details.\nhh_server: stopped.",
5801 "shortMessage": "Hack: failed",
5804 result
={"title": "Restart Hack IDE"},
5806 .wait_for_server_request(
5807 method
="window/showStatus",
5809 "actions": [{"title": "Restart hh_server"}],
5810 "message": "Hack IDE: ready.\nhh_server: stopped.",
5811 "shortMessage": "Hack: ready",
5814 result
=NoResponse(),
5818 comment
="hover after restart will succeed",
5819 method
="textDocument/hover",
5821 "textDocument": {"uri": "${php_file_uri}"},
5822 "position": {"line": 26, "character": 20},
5824 result
={"contents": [{"language": "hack", "value": "string"}]},
5825 powered_by
="serverless_ide",
5828 method
="textDocument/didClose",
5829 params
={"textDocument": {"uri": "${php_file_uri}"}},
5833 comment
="hover after file_close will fail",
5834 method
="textDocument/hover",
5836 "textDocument": {"uri": "${php_file_uri}"},
5837 "position": {"line": 26, "character": 20},
5841 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5842 .notification(method
="exit", params
={})
5845 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5847 def test_hh_server_status_diagnostic(self
) -> None:
5848 variables
= dict(self
.prepare_serverless_ide_environment())
5849 variables
.update(self
.setup_php_file("unsaved1.php"))
5852 "unsaved2_file_uri": self
.repo_file_uri("unsaved2.php"),
5853 "unsaved2_file": self
.read_repo_file("unsaved2.php"),
5856 self
.test_driver
.stop_hh_server()
5859 self
.initialize_spec(
5860 LspTestSpec("test_hh_server_status_diagnostic"), use_serverless_ide
=True
5862 .ignore_status_diagnostics(False)
5864 method
="textDocument/didOpen",
5867 "uri": "${php_file_uri}",
5868 "languageId": "hack",
5870 "text": "${php_file}",
5874 .wait_for_notification(
5875 comment
="After didOpen(file1), the hh_server_status diagnostic should appear in file1",
5876 method
="textDocument/publishDiagnostics",
5878 "uri": "${php_file_uri}",
5882 "start": {"line": 0, "character": 0},
5883 "end": {"line": 0, "character": 1},
5886 "source": "hh_server",
5887 "message": "hh_server isn't running, so there may be undetected errors. Try `hh` at the command line... hh_server: stopped.",
5888 "relatedInformation": [],
5889 "relatedLocations": [],
5896 method
="textDocument/didOpen",
5899 "uri": "${unsaved2_file_uri}",
5900 "languageId": "hack",
5902 "text": "${unsaved2_file}",
5906 .wait_for_notification(
5907 comment
="After didOpen(file2), the hh_server_status diagnostic should disappear from file1",
5908 method
="textDocument/publishDiagnostics",
5910 "uri": "${php_file_uri}",
5915 .wait_for_notification(
5916 comment
="After didOpen(file2), the hh_server_status diagnostic should reappear in file2",
5917 method
="textDocument/publishDiagnostics",
5919 "uri": "${unsaved2_file_uri}",
5923 "start": {"line": 0, "character": 0},
5924 "end": {"line": 0, "character": 1},
5927 "source": "hh_server",
5928 "message": "hh_server isn't running, so there may be undetected errors. Try `hh` at the command line... hh_server: stopped.",
5929 "relatedInformation": [],
5930 "relatedLocations": [],
5937 method
="textDocument/didClose",
5938 params
={"textDocument": {"uri": "${unsaved2_file_uri}"}},
5940 .wait_for_notification(
5941 comment
="After didClose(file2), the hh_server_status diagnostic should disappear from file2",
5942 method
="textDocument/publishDiagnostics",
5944 "uri": "${unsaved2_file_uri}",
5949 .wait_for_notification(
5950 comment
="After didClose(file2), the hh_server_status diagnostic should reappear in file1",
5951 method
="textDocument/publishDiagnostics",
5953 "uri": "${php_file_uri}",
5957 "start": {"line": 0, "character": 0},
5958 "end": {"line": 0, "character": 1},
5961 "source": "hh_server",
5962 "message": "hh_server isn't running, so there may be undetected errors. Try `hh` at the command line... hh_server: stopped.",
5963 "relatedInformation": [],
5964 "relatedLocations": [],
5971 method
="textDocument/didClose",
5972 params
={"textDocument": {"uri": "${php_file_uri}"}},
5974 .wait_for_notification(
5975 comment
="After didClose(file1), the hh_server_status diagnostic should disappear from file1",
5976 method
="textDocument/publishDiagnostics",
5978 "uri": "${php_file_uri}",
5983 .request(line
=line(), method
="shutdown", params
={}, result
=None)
5984 .notification(method
="exit", params
={})
5987 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
5989 def _sanitize_gutter_line_numbers(self
, s
: str) -> str:
5990 gutter_line_number_re
= re
.compile(r
"^[ ]*[0-9]+ \|", re
.MULTILINE
)
5991 return re
.sub(gutter_line_number_re
, " XXXX |", s
)
5993 def test_lsptestspec_incorrect_request_result(self
) -> None:
5994 variables
= dict(self
.prepare_serverless_ide_environment())
5995 variables
.update(self
.setup_php_file("hover.php"))
5996 self
.test_driver
.stop_hh_server()
5999 self
.initialize_spec(
6000 LspTestSpec("test_lsptestspec_incorrect_request_result"),
6001 use_serverless_ide
=True,
6004 method
="textDocument/didOpen",
6007 "uri": "${php_file_uri}",
6008 "languageId": "hack",
6010 "text": "${php_file}",
6016 comment
="hover over function invocation",
6017 method
="textDocument/hover",
6019 "textDocument": {"uri": "${php_file_uri}"},
6020 "position": {"line": 3, "character": 16},
6024 {"language": "hack", "value": "int"},
6025 "INCORRECT COMMENT HERE",
6028 "start": {"line": 3, "character": 9},
6029 "end": {"line": 3, "character": 16},
6032 powered_by
="serverless_ide",
6034 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6035 .notification(method
="exit", params
={})
6040 variables
=variables
,
6041 wait_for_server
=False,
6042 use_serverless_ide
=True,
6044 raise AssertionError("Expected an error here")
6045 except AssertionError as e
:
6047 self
._sanitize
_gutter
_line
_numbers
(str(e
)),
6049 Test case test_lsptestspec_incorrect_request_result failed with 1 errors:
6052 Description: Request with ID 5 (comment: 'hover over function invocation') \
6053 got an incorrect result:
6055 (- is expected lines, + is actual lines)
6056 - {'contents': [{'language': 'hack', 'value': 'int'}, 'INCORRECT COMMENT HERE'],
6057 ? ---------------------------
6059 + {'contents': [{'language': 'hack', 'value': 'int'},
6060 + 'A comment describing b_hover.'],
6061 'range': {'end': {'character': 16, 'line': 3},
6062 'start': {'character': 9, 'line': 3}}}
6065 This was the associated request:
6067 hphp/hack/test/integration/test_lsp.py
6070 XXXX | comment="hover over function invocation",
6071 XXXX | method="textDocument/hover",
6073 XXXX | "textDocument": {"uri": "${php_file_uri}"},
6074 XXXX | "position": {"line": 3, "character": 16},
6077 XXXX | "contents": [
6078 XXXX | {"language": "hack", "value": "int"},
6079 XXXX | "INCORRECT COMMENT HERE",
6082 XXXX | "start": {"line": 3, "character": 9},
6083 XXXX | "end": {"line": 3, "character": 16},
6086 XXXX | powered_by="serverless_ide",
6090 1) If this was unexpected, then the language server is buggy and should be
6093 2) If this was expected, you can update your request with the following code to
6098 comment='hover over function invocation',
6099 method='textDocument/hover',
6100 params={'textDocument': {'uri': '${php_file_uri}'}, \
6101 'position': {'line': 3, 'character': 16}},
6102 result={'contents': [{'language': 'hack', 'value': 'int'}, \
6103 'A comment describing b_hover.'], \
6104 'range': {'start': {'line': 3, 'character': 9}, \
6105 'end': {'line': 3, 'character': 16}}},
6106 powered_by='serverless_ide',
6109 If you want to examine the raw LSP logs, you can check the `.sent.log` and
6110 `.received.log` files that were generated in the template repo for this test.\
6114 def test_lsptestspec_unexpected_notification(self
) -> None:
6115 self
.prepare_server_environment()
6116 variables
= self
.setup_php_file("didchange.php")
6118 self
.initialize_spec(LspTestSpec("did_change"), use_serverless_ide
=False)
6119 .wait_for_hh_server_ready()
6121 method
="textDocument/didOpen",
6124 "uri": "${php_file_uri}",
6125 "languageId": "hack",
6127 "text": "${php_file}",
6132 method
="textDocument/didChange",
6134 "textDocument": {"uri": "${php_file_uri}"},
6138 "start": {"line": 7, "character": 11},
6139 "end": {"line": 7, "character": 12},
6146 .wait_for_notification(
6147 method
="textDocument/publishDiagnostics",
6149 "uri": "${php_file_uri}",
6153 "start": {"line": 7, "character": 11},
6154 "end": {"line": 7, "character": 11},
6159 "message": "A semicolon ; is expected here.",
6160 "relatedLocations": [],
6161 "relatedInformation": [],
6166 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6167 .notification(method
="exit", params
={})
6171 spec
, variables
, wait_for_server
=True, use_serverless_ide
=False
6173 raise AssertionError("Expected an error here")
6174 except AssertionError as e
:
6176 self
._sanitize
_gutter
_line
_numbers
(str(e
)),
6178 Test case did_change failed with 1 errors:
6181 Description: An unexpected notification of type \
6182 'textDocument/publishDiagnostics' was sent by the language server.
6183 Here is the notification payload:
6186 'method': 'textDocument/publishDiagnostics',
6187 'params': {'diagnostics': [],
6188 'uri': '__PHP_FILE_URI__'}}
6191 This was the most recent request issued from the language client before it
6192 received the notification:
6194 hphp/hack/test/integration/test_lsp.py
6195 XXXX | .request(line=line(), method="shutdown", params={}, result=None)
6198 1) If this was unexpected, then the language server is buggy and should be
6201 2) If all notifications of type 'textDocument/publishDiagnostics' should be \
6202 ignored, add this directive
6203 anywhere in your test:
6205 .ignore_notifications(method='textDocument/publishDiagnostics')
6207 3) If this single instance of the notification was expected, add this directive
6208 to your test to wait for it before proceeding:
6210 .wait_for_notification(
6211 method='textDocument/publishDiagnostics',
6212 params={'uri': '${php_file_uri}', 'diagnostics': []},
6215 If you want to examine the raw LSP logs, you can check the `.sent.log` and
6216 `.received.log` files that were generated in the template repo for this test.\
6218 # There's an instance of a literal `${php_file_uri}` in there
6219 # which we don't want to change, so use a different name than
6221 .replace("__PHP_FILE_URI__", variables
["php_file_uri"]),
6224 def test_serverless_ide_highlight(self
) -> None:
6225 variables
= dict(self
.prepare_serverless_ide_environment())
6226 variables
.update(self
.setup_php_file("highlight.php"))
6227 self
.test_driver
.stop_hh_server()
6230 self
.initialize_spec(
6231 LspTestSpec("serverless_ide_highlight"), use_serverless_ide
=True
6234 method
="textDocument/didOpen",
6237 "uri": "${php_file_uri}",
6238 "languageId": "hack",
6240 "text": "${php_file}",
6246 comment
="document highlight, id 2",
6247 method
="textDocument/documentHighlight",
6249 "textDocument": {"uri": "${php_file_uri}"},
6250 "position": {"line": 3, "character": 10},
6255 "start": {"line": 3, "character": 9},
6256 "end": {"line": 3, "character": 20},
6260 powered_by
="serverless_ide",
6264 comment
="shutdown, id 3",
6270 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6272 def test_serverless_ide_coverage(self
) -> None:
6273 variables
= dict(self
.prepare_serverless_ide_environment())
6274 variables
.update(self
.setup_php_file("coverage.php"))
6275 self
.test_driver
.stop_hh_server()
6278 self
.initialize_spec(
6279 LspTestSpec("serverless_ide_coverage"), use_serverless_ide
=True
6282 method
="textDocument/didOpen",
6285 "uri": "${php_file_uri}",
6286 "languageId": "hack",
6288 "text": "${php_file}",
6294 comment
="Check type coverage",
6295 method
="textDocument/typeCoverage",
6296 params
={"textDocument": {"uri": "${php_file_uri}"}},
6298 "coveredPercent": 100,
6299 "uncoveredRanges": [],
6300 "defaultMessage": "Un-type checked code. Consider adding type annotations.",
6302 powered_by
="serverless_ide",
6312 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6314 def test_status_stopped(self
) -> None:
6315 self
.prepare_server_environment()
6316 variables
= self
.setup_php_file("hover.php")
6317 self
.test_driver
.stop_hh_server()
6320 self
.initialize_spec(
6321 LspTestSpec("status_stopped"),
6322 use_serverless_ide
=False,
6323 supports_status
=True,
6325 .wait_for_server_request(
6326 method
="window/showStatus",
6328 "shortMessage": "Hack: stopped",
6329 "message": "hh_server: stopped.",
6330 "actions": [{"title": "Restart hh_server"}],
6333 result
=NoResponse(),
6335 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6336 .notification(method
="exit", params
={})
6338 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=False)
6340 def test_status_running(self
) -> None:
6341 self
.prepare_server_environment()
6342 variables
= self
.setup_php_file("hover.php")
6345 self
.initialize_spec(
6346 LspTestSpec("status_running"),
6347 use_serverless_ide
=False,
6348 supports_status
=True,
6351 comment
="Ignore initializing... requests since they're racy",
6352 method
="window/showStatus",
6355 "shortMessage": "Hack: initializing",
6356 "message": "hh_server initializing: processing [<test> seconds]",
6360 .wait_for_server_request(
6361 method
="window/showStatus",
6362 params
={"actions": [], "message": "hh_server: ready.", "type": 3},
6363 result
=NoResponse(),
6365 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6366 .notification(method
="exit", params
={})
6368 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
6370 def test_serverless_ide_status_stopped(self
) -> None:
6371 variables
= dict(self
.prepare_serverless_ide_environment())
6372 variables
.update(self
.setup_php_file("hover.php"))
6373 self
.test_driver
.stop_hh_server()
6376 self
.initialize_spec(
6377 LspTestSpec("serverless_ide_status_stopped"),
6378 use_serverless_ide
=True,
6379 supports_status
=True,
6382 comment
="ignore initializing... messages since they're kind of racy",
6383 method
="window/showStatus",
6386 "actions": [{"title": "Restart hh_server"}],
6387 "message": "Hack IDE: initializing.\nhh_server: stopped.",
6388 "shortMessage": "Hack: initializing",
6392 comment
="another racy initialization to ignore, before hh_server has even reported its status",
6393 method
="window/showStatus",
6397 "message": "Hack IDE: initializing.",
6398 "shortMessage": "Hack: initializing",
6402 comment
="another racy initialization to ignore, again before hh_server",
6403 method
="window/showStatus",
6407 "message": "Hack IDE: ready.",
6408 "shortMessage": "Hack: ready",
6411 .wait_for_server_request(
6412 method
="window/showStatus",
6414 "message": "Hack IDE: ready.\nhh_server: stopped.",
6415 "shortMessage": "Hack: ready",
6416 "actions": [{"title": "Restart hh_server"}],
6419 result
=NoResponse(),
6421 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6422 .notification(method
="exit", params
={})
6424 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6426 def test_serverless_ide_status_restart(self
) -> None:
6427 variables
= dict(self
.prepare_serverless_ide_environment())
6428 variables
.update(self
.setup_php_file("hover.php"))
6431 self
.initialize_spec(
6432 LspTestSpec("serverless_ide_status_restart"),
6433 use_serverless_ide
=True,
6434 supports_status
=True,
6437 comment
="Ignore initializing messages since they're racy",
6438 method
="window/showStatus",
6442 "message": "Hack IDE: initializing.\nhh_server initializing: processing [<test> seconds]",
6443 "shortMessage": "Hack: initializing",
6447 comment
="Another form of initializing to ignore",
6448 method
="window/showStatus",
6452 "message": "Hack IDE: initializing.\nhh_server: ready.",
6453 "shortMessage": "Hack: initializing",
6457 comment
="Another form of initializing to ignore before we've even heard the first peep from hh_server",
6458 method
="window/showStatus",
6462 "message": "Hack IDE: initializing.",
6463 "shortMessage": "Hack: initializing",
6467 comment
="another racy initialization to ignore, again before hh_server",
6468 method
="window/showStatus",
6472 "message": "Hack IDE: ready.",
6473 "shortMessage": "Hack: ready",
6476 .wait_for_server_request(
6477 method
="window/showStatus",
6480 "message": "Hack IDE: ready.\nhh_server: ready.",
6481 "shortMessage": "Hack: ready",
6484 result
=NoResponse(),
6488 method
="$test/shutdownServerlessIde",
6491 powered_by
="serverless_ide",
6493 .wait_for_server_request(
6494 method
="window/showStatus",
6496 "actions": [{"title": "Restart Hack IDE"}],
6497 "message": "Hack IDE has failed. See Output›Hack for details.\nhh_server: ready.",
6498 "shortMessage": "Hack: failed",
6501 result
={"title": "Restart Hack IDE"},
6503 .wait_for_server_request(
6504 method
="window/showStatus",
6507 "message": "Hack IDE: ready.\nhh_server: ready.",
6508 "shortMessage": "Hack: ready",
6511 result
=NoResponse(),
6513 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6514 .notification(method
="exit", params
={})
6516 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=True)
6518 def test_serverless_ide_failed_to_load_saved_state(self
) -> None:
6519 variables
= dict(self
.prepare_serverless_ide_environment())
6520 variables
.update(self
.setup_php_file("hover.php"))
6521 assert "naming_table_saved_state_path" in variables
6522 variables
["naming_table_saved_state_path"] = "/tmp/nonexistent"
6525 self
.initialize_spec(
6526 LspTestSpec("serverless_ide_status_failed_to_load_saved_state"),
6527 use_serverless_ide
=True,
6528 supports_status
=True,
6532 comment
="Ignore initializing since they're kind of racy",
6533 method
="window/showStatus",
6537 "message": "Hack IDE: initializing.\nhh_server initializing: processing [<test> seconds]",
6538 "shortMessage": "Hack: initializing",
6542 comment
="Ignore another form of initializing",
6543 method
="window/showStatus",
6547 "message": "Hack IDE: initializing.\nhh_server: ready.",
6548 "shortMessage": "Hack: initializing",
6552 comment
="Ignore another form of initializing, from before we've even heard the first peep out of hh_server",
6553 method
="window/showStatus",
6557 "message": "Hack IDE: initializing.",
6558 "shortMessage": "Hack: initializing",
6562 comment
="Ignore another form of initializing, again before hh_server",
6563 method
="window/showStatus",
6566 "actions": [{"title": "Restart Hack IDE"}],
6567 "message": "Hack IDE has failed. See Output›Hack for details.",
6568 "shortMessage": "Hack: failed",
6571 .wait_for_notification(
6572 method
="window/logMessage",
6575 "message": "Hack IDE has failed.\nThis is unexpected.\nPlease file a bug within your IDE.\nMore details: http://dummy/HH_TEST_MODE",
6578 .wait_for_server_request(
6579 method
="window/showStatus",
6581 "actions": [{"title": "Restart Hack IDE"}],
6582 "message": "Hack IDE has failed. See Output›Hack for details.\nhh_server: ready.",
6583 "shortMessage": "Hack: failed",
6586 result
=NoResponse(),
6588 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6589 .notification(method
="exit", params
={})
6591 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=True)
6593 def test_workspace_symbol(self
) -> None:
6594 self
.prepare_server_environment()
6595 variables
= self
.setup_php_file("didchange.php")
6597 self
.initialize_spec(
6598 LspTestSpec("test_workspace_symbol"), use_serverless_ide
=False
6600 .wait_for_hh_server_ready()
6603 comment
="Look up symbols",
6604 method
="workspace/symbol",
6605 params
={"query": "TestNS\\test"},
6608 "name": "TestNS\\test_func",
6611 "uri": "file://${root_path}/completion_extras_namespace.php",
6613 "start": {"line": 4, "character": 9},
6614 "end": {"line": 4, "character": 25},
6622 comment
="Look up symbols starting with 'test_f' within multiple namespaces",
6623 method
="workspace/symbol",
6624 params
={"query": "test_f"},
6627 "name": "test_function",
6630 "uri": "file://${root_path}/completion.php",
6632 "start": {"line": 7, "character": 9},
6633 "end": {"line": 7, "character": 22},
6638 "name": "TestNS\\test_func",
6641 "uri": "file://${root_path}/completion_extras_namespace.php",
6643 "start": {"line": 4, "character": 9},
6644 "end": {"line": 4, "character": 25},
6650 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6651 .notification(method
="exit", params
={})
6653 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=False)
6655 def test_serverless_ide_during_hh_server_restart(self
) -> None:
6656 variables
= dict(self
.prepare_serverless_ide_environment())
6657 variables
.update(self
.setup_php_file("didchange.php"))
6659 self
.initialize_spec(
6660 LspTestSpec("test_serverless_ide_during_hh_server_restart"),
6661 use_serverless_ide
=True,
6664 method
="textDocument/didOpen",
6667 "uri": "${php_file_uri}",
6668 "languageId": "hack",
6670 "text": "${php_file}",
6675 comment
="Send a 'didChange' notification before HH Server is functional.",
6676 method
="textDocument/didChange",
6678 "textDocument": {"uri": "${php_file_uri}"},
6682 "start": {"line": 7, "character": 9},
6683 "end": {"line": 7, "character": 11},
6690 .start_hh_server("Start HH Server; should detect the bad edit")
6691 .wait_for_notification(
6692 method
="textDocument/publishDiagnostics",
6694 "uri": "${php_file_uri}",
6698 "message": "Invalid return type",
6700 "end": {"character": 14, "line": 7},
6701 "start": {"character": 9, "line": 7},
6703 "relatedInformation": [
6707 "end": {"character": 27, "line": 6},
6708 "start": {"character": 24, "line": 6},
6710 "uri": "${php_file_uri}",
6712 "message": "Expected int",
6717 "end": {"character": 14, "line": 7},
6718 "start": {"character": 9, "line": 7},
6720 "uri": "${php_file_uri}",
6722 "message": "But got string",
6725 "relatedLocations": [
6729 "end": {"character": 27, "line": 6},
6730 "start": {"character": 24, "line": 6},
6732 "uri": "${php_file_uri}",
6734 "message": "Expected int",
6739 "end": {"character": 14, "line": 7},
6740 "start": {"character": 9, "line": 7},
6742 "uri": "${php_file_uri}",
6744 "message": "But got string",
6753 .stop_hh_server("Shutdown HH Server")
6754 .start_hh_server("Restart HH Server")
6755 .wait_for_notification(
6756 comment
="On startup it thinks everything is okay ...",
6757 method
="textDocument/publishDiagnostics",
6758 params
={"uri": "${php_file_uri}", "diagnostics": []},
6760 .wait_for_notification(
6761 comment
="But then hh_server sends a hello message and it gets the edited files, which leads it to see the problem.",
6762 method
="textDocument/publishDiagnostics",
6764 "uri": "${php_file_uri}",
6768 "message": "Invalid return type",
6770 "end": {"character": 14, "line": 7},
6771 "start": {"character": 9, "line": 7},
6773 "relatedInformation": [
6777 "end": {"character": 27, "line": 6},
6778 "start": {"character": 24, "line": 6},
6780 "uri": "${php_file_uri}",
6782 "message": "Expected int",
6787 "end": {"character": 14, "line": 7},
6788 "start": {"character": 9, "line": 7},
6790 "uri": "${php_file_uri}",
6792 "message": "But got string",
6795 "relatedLocations": [
6799 "end": {"character": 27, "line": 6},
6800 "start": {"character": 24, "line": 6},
6802 "uri": "${php_file_uri}",
6804 "message": "Expected int",
6809 "end": {"character": 14, "line": 7},
6810 "start": {"character": 9, "line": 7},
6812 "uri": "${php_file_uri}",
6814 "message": "But got string",
6823 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6824 .notification(method
="exit", params
={})
6826 self
.run_spec(spec
, variables
, wait_for_server
=True, use_serverless_ide
=True)
6828 def test_serverless_ide_naming_error1(self
) -> None:
6829 variables
= dict(self
.prepare_serverless_ide_environment())
6830 variables
.update(self
.setup_php_file("didchange.php"))
6833 "main_file": self
.repo_file("main.php"),
6834 "main_file_contents": """\
6836 function main(): int {
6840 "file_a": self
.repo_file("a.php"),
6841 "file_b": self
.repo_file("b.php"),
6845 self
.initialize_spec(
6846 LspTestSpec("serverless_ide_naming_error1"), use_serverless_ide
=True
6849 uri
="${main_file}", contents
="${main_file_contents}", notify
=True
6852 method
="textDocument/didOpen",
6855 "uri": "${main_file}",
6856 "languageId": "hack",
6858 "text": "${main_file_contents}",
6864 comment
="Ensure that hover over `aaa` works even when the name is not yet defined",
6865 method
="textDocument/hover",
6867 "textDocument": {"uri": "${main_file}"},
6868 "position": {"line": 2, "character": 13},
6871 "contents": [{"language": "hack", "value": "_"}],
6873 "start": {"line": 2, "character": 11},
6874 "end": {"line": 2, "character": 14},
6877 powered_by
="serverless_ide",
6880 comment
="create file A",
6884 function aaa(): int {
6892 comment
="Ensure that hover over `aaa` works when there are no naming errors",
6893 method
="textDocument/hover",
6895 "textDocument": {"uri": "${main_file}"},
6896 "position": {"line": 2, "character": 13},
6900 {"language": "hack", "value": "function aaa(): int"},
6903 "start": {"line": 2, "character": 11},
6904 "end": {"line": 2, "character": 14},
6907 powered_by
="serverless_ide",
6910 comment
="create file B",
6914 function aaa(): string {
6922 comment
="Ensure that hover over `aaa` works even when there is a duplicate name",
6923 method
="textDocument/hover",
6925 "textDocument": {"uri": "${main_file}"},
6926 "position": {"line": 2, "character": 13},
6930 {"language": "hack", "value": "function aaa(): int"},
6933 "start": {"line": 2, "character": 11},
6934 "end": {"line": 2, "character": 14},
6937 powered_by
="serverless_ide",
6940 comment
="delete file A", uri
="${file_a}", contents
=None, notify
=True
6944 comment
="Now that we've fixed the error, hover should work.",
6945 method
="textDocument/hover",
6947 "textDocument": {"uri": "${main_file}"},
6948 "position": {"line": 2, "character": 13},
6952 {"language": "hack", "value": "function aaa(): string"},
6955 "start": {"line": 2, "character": 11},
6956 "end": {"line": 2, "character": 14},
6959 powered_by
="serverless_ide",
6961 .request(line
=line(), method
="shutdown", params
={}, result
=None)
6962 .notification(method
="exit", params
={})
6964 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
6966 def test_serverless_ide_naming_error2(self
) -> None:
6967 variables
= dict(self
.prepare_serverless_ide_environment())
6968 self
.test_driver
.stop_hh_server()
6969 variables
.update(self
.setup_php_file("naming_error_caller.php"))
6972 "contents": self
.read_repo_file("naming_error_declaration.php"),
6973 "original": self
.repo_file("naming_error_declaration.php"),
6974 "copy": self
.repo_file("naming_error_copy.php"),
6978 self
.initialize_spec(
6979 LspTestSpec("serverless_ide_naming_error2"), use_serverless_ide
=True
6982 method
="textDocument/didOpen",
6985 "uri": "${php_file_uri}",
6986 "languageId": "hack",
6988 "text": "${php_file}",
6993 comment
="create copy",
6995 contents
="${contents}",
6999 comment
="delete copy", uri
="${copy}", contents
=None, notify
=True
7003 comment
="hover should work fine after making copy then deleting copy.",
7004 method
="textDocument/hover",
7006 "textDocument": {"uri": "${php_file_uri}"},
7007 "position": {"line": 3, "character": 15},
7013 "value": "function naming_error_declaration(): void",
7017 "start": {"line": 3, "character": 2},
7018 "end": {"line": 3, "character": 26},
7021 powered_by
="serverless_ide",
7023 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7024 .notification(method
="exit", params
={})
7026 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7028 def test_serverless_ide_naming_error3(self
) -> None:
7029 variables
= dict(self
.prepare_serverless_ide_environment())
7030 self
.test_driver
.stop_hh_server()
7031 variables
.update(self
.setup_php_file("naming_error_caller.php"))
7034 "contents": self
.read_repo_file("naming_error_declaration.php"),
7035 "original": self
.repo_file("naming_error_declaration.php"),
7036 "copy": self
.repo_file("naming_error_copy.php"),
7040 self
.initialize_spec(
7041 LspTestSpec("serverless_ide_naming_error3"), use_serverless_ide
=True
7044 method
="textDocument/didOpen",
7047 "uri": "${php_file_uri}",
7048 "languageId": "hack",
7050 "text": "${php_file}",
7055 comment
="create copy",
7057 contents
="${contents}",
7061 comment
="delete original", uri
="${original}", contents
=None, notify
=True
7065 comment
="hover should work fine after making copy then deleting original.",
7066 method
="textDocument/hover",
7068 "textDocument": {"uri": "${php_file_uri}"},
7069 "position": {"line": 3, "character": 15},
7075 "value": "function naming_error_declaration(): void",
7079 "start": {"line": 3, "character": 2},
7080 "end": {"line": 3, "character": 26},
7083 powered_by
="serverless_ide",
7085 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7086 .notification(method
="exit", params
={})
7088 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7090 def test_serverless_ide_requests_before_init(self
) -> None:
7091 variables
= dict(self
.prepare_serverless_ide_environment())
7092 variables
["root_path"] = self
.test_driver
.repo_dir
7093 self
.test_driver
.stop_hh_server()
7096 self
.initialize_spec(
7097 LspTestSpec("test_serverless_ide_requests_before_init"),
7098 use_serverless_ide
=True,
7099 supports_status
=True,
7102 .ignore_notifications(method
="textDocument/publishDiagnostics")
7104 comment
="Ignore 'initializing...' messages since they're racy",
7105 method
="window/showStatus",
7108 "actions": [{"title": "Restart hh_server"}],
7109 "message": "Hack IDE: initializing.\nhh_server: stopped.",
7110 "shortMessage": "Hack: initializing",
7114 comment
="another racy initialization, before we've yet heard from hh_server",
7115 method
="window/showStatus",
7119 "message": "Hack IDE: initializing.",
7120 "shortMessage": "Hack: initializing",
7124 comment
="another racy initialization, if HackIDE is done before hh_server has yet sent status",
7125 method
="window/showStatus",
7129 "message": "Hack IDE: ready.",
7130 "shortMessage": "Hack: ready",
7136 uri
="file://${root_path}/beforeInit1.php",
7137 contents
="<?hh // strict\nfunction beforeInit1(): int {\n return 42;\n}\n",
7140 comment
="open a file before init has finished",
7141 method
="textDocument/didOpen",
7144 "uri": "file://${root_path}/beforeInit2.php",
7145 "languageId": "hack",
7147 "text": "<?hh // strict\nfunction beforeInit2(): void {\n $foo = beforeInit1();\n}\n",
7153 comment
="hover before init will fail",
7154 method
="textDocument/hover",
7156 "textDocument": {"uri": "file://${root_path}/beforeInit2.php"},
7157 "position": {"line": 2, "character": 4},
7163 comment
="documentSymbol before init will succeed",
7164 method
="textDocument/documentSymbol",
7165 params
={"textDocument": {"uri": "file://${root_path}/beforeInit2.php"}},
7168 "name": "beforeInit2",
7171 "uri": "file://${root_path}/beforeInit2.php",
7173 "start": {"line": 1, "character": 0},
7174 "end": {"line": 3, "character": 1},
7179 powered_by
="serverless_ide",
7181 .wait_for_notification(
7182 comment
="wait for sIDE to init",
7183 method
="telemetry/event",
7184 params
={"type": 4, "message": "[client-ide] Finished init: ok"},
7186 .wait_for_server_request(
7187 method
="window/showStatus",
7189 "actions": [{"title": "Restart hh_server"}],
7190 "message": "Hack IDE: ready.\nhh_server: stopped.",
7191 "shortMessage": "Hack: ready",
7194 result
=NoResponse(),
7198 comment
="hover after init will succeed",
7199 method
="textDocument/hover",
7201 "textDocument": {"uri": "file://${root_path}/beforeInit2.php"},
7202 "position": {"line": 2, "character": 4},
7205 "contents": [{"language": "hack", "value": "int"}],
7207 "start": {"line": 2, "character": 2},
7208 "end": {"line": 2, "character": 6},
7211 powered_by
="serverless_ide",
7213 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7214 .notification(method
="exit", params
={})
7217 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)
7219 def test_serverless_ide_workspace_symbol(self
) -> None:
7220 variables
= dict(self
.prepare_serverless_ide_environment())
7221 variables
["root_path"] = self
.test_driver
.repo_dir
7222 self
.test_driver
.stop_hh_server()
7225 self
.initialize_spec(
7226 LspTestSpec("serverless_ide_workspace_symbol"), use_serverless_ide
=True
7230 comment
="workspace symbol call, global, powered by sqlite (generated during serverless-ide-init)",
7231 method
="workspace/symbol",
7232 params
={"query": "TakesString"},
7235 "name": "TakesString",
7238 "uri": "file://${root_path}/definition.php",
7240 "start": {"line": 36, "character": 6},
7241 "end": {"line": 36, "character": 17},
7246 powered_by
="serverless_ide",
7250 comment
="workspace symbol call, member (derived from naming-table)",
7251 method
="workspace/symbol",
7252 params
={"query": "TakesString::"},
7255 "name": "__construct",
7258 "uri": "file://${root_path}/definition.php",
7260 "start": {"line": 37, "character": 18},
7261 "end": {"line": 37, "character": 29},
7266 powered_by
="serverless_ide",
7268 .request(line
=line(), method
="shutdown", params
={}, result
=None)
7269 .notification(method
="exit", params
={})
7271 self
.run_spec(spec
, variables
, wait_for_server
=False, use_serverless_ide
=True)