2 * Copyright (c) 2016, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
14 module Test
= Integration_test_base
16 let pos_at (line1
, column1
) (line2
, column2
) =
17 Some
(Pos.make_from_file_pos
19 (File_pos.of_line_column_offset line1
(column1
- 1) 0)
20 (File_pos.of_line_column_offset line2 column2
0))
22 let class_members = "<?hh // strict
23 abstract class ClassMembers {
24 public async function genDoStuff(): Awaitable<void> {}
26 public string $public = 'public';
27 protected string $protected = 'protected';
28 private string $private = 'private';
30 public static string $staticVar = 'staticVar';
32 public abstract function abstractMethod(): string;
34 public final function finalMethod(string $arg): void {}
36 protected final static async function genLotsOfModifiers(): Awaitable<void> {}
38 public async function exerciseClassMembers(): Awaitable<void> {
39 await $this->genDoStuff();
47 ClassMembers::$staticVar;
49 $this->abstractMethod();
51 $this->finalMethod(\"arg\");
53 await ClassMembers::genLotsOfModifiers();
58 let class_members_cases = [
59 ("class_members.php", 18, 18), [
61 snippet
= "public async function genDoStuff(): Awaitable<void>";
62 addendum
= ["Full name: `ClassMembers::genDoStuff`"];
63 pos
= pos_at (18, 18) (18, 27);
66 ("class_members.php", 20, 12), [
68 snippet
= "public string ClassMembers::public";
70 pos
= pos_at (20, 12) (20, 17);
73 ("class_members.php", 22, 12), [
75 snippet
= "protected string ClassMembers::protected";
77 pos
= pos_at (22, 12) (22, 20);
80 ("class_members.php", 24, 12), [
82 snippet
= "private string ClassMembers::private";
84 pos
= pos_at (24, 12) (24, 18);
87 ("class_members.php", 26, 19), [
89 snippet
= "public static string ClassMembers::staticVar";
91 pos
= pos_at (26, 19) (26, 28);
94 ("class_members.php", 28, 12), [
96 snippet
= "public abstract function abstractMethod(): string";
97 addendum
= ["Full name: `ClassMembers::abstractMethod`"];
98 pos
= pos_at (28, 12) (28, 25);
101 ("class_members.php", 30, 12), [
103 snippet
= "public final function finalMethod(string $arg): void";
104 addendum
= ["Full name: `ClassMembers::finalMethod`"];
105 pos
= pos_at (30, 12) (30, 22);
108 ("class_members.php", 32, 11), [
110 snippet
= "abstract class ClassMembers";
112 pos
= pos_at (32, 11) (32, 22);
115 ("class_members.php", 32, 25), [
117 snippet
= "protected final static async\n\
118 function genLotsOfModifiers(): Awaitable<void>";
119 addendum
= ["Full name: `ClassMembers::genLotsOfModifiers`"];
120 pos
= pos_at (32, 25) (32, 42);
125 let classname_call = "<?hh // strict
126 class ClassnameCall {
127 static function foo(): int {
132 function call_foo(): void {
133 ClassnameCall::foo();
137 let classname_call_cases = [
138 ("classname_call.php", 9, 4), [{
139 snippet
= "class ClassnameCall";
141 pos
= pos_at (9, 3) (9, 15);
143 ("classname_call.php", 9, 18), [{
144 snippet
= "static function foo(): int";
145 addendum
= ["Full name: `ClassnameCall::foo`"];
146 pos
= pos_at (9, 18) (9, 20);
150 let chained_calls = "<?hh // strict
152 public function foo(): this {
157 function test(): void {
158 $myItem = new ChainedCalls();
166 let chained_calls_cases = [
167 ("chained_calls.php", 13, 8), [
169 snippet
= "public function foo(): ChainedCalls";
170 addendum
= ["Full name: `ChainedCalls::foo`"];
171 pos
= pos_at (13, 7) (13, 9);
176 let multiple_potential_types = "<?hh // strict
177 class C1 { public function foo(): int { return 5; } }
178 class C2 { public function foo(): string { return 's'; } }
179 function test_multiple_type(C1 $c1, C2 $c2, bool $cond): arraykey {
180 $x = $cond ? $c1 : $c2;
185 let multiple_potential_types_cases = [
186 ("multiple_potential_types.php", 6, 11), [{
187 snippet
= "(C1 | C2)";
191 ("multiple_potential_types.php", 6, 16), [
193 snippet
= "((function(): string) | (function(): int))";
198 snippet
= "((function(): string) | (function(): int))";
205 let classname_variable = "<?hh // strict
206 class ClassnameVariable {
207 public static function foo(): void {}
210 function test_classname(): void {
211 $cls = ClassnameVariable::class;
216 let classname_variable_cases = [
217 ("classname_variable.php", 8, 4), [{
218 snippet
= "classname<ClassnameVariable>";
220 pos
= pos_at (8, 3) (8, 6);
223 ("classname_variable.php", 8, 10), [{
224 snippet
= "public static function foo(): void";
225 addendum
= ["Full name: `ClassnameVariable::foo`"];
226 pos
= pos_at (8, 9) (8, 11);
230 let docblock = "<?hh // strict
235 function queryDocBlocks(): void {
240 DocBlock::preserveIndentation();
242 DocBlock::leadingStarsAndMDList();
244 DocBlock::manyLineBreaks();
246 $x = new DocBlockOnClassButNotConstructor();
250 function docblockReturn(): DocBlockBase {
252 $x = new DocBlockBase();
254 return new DocBlockDerived();
266 /** Method doc block with double star. */
267 public static function doStuff(): void {}
269 /** Multiline doc block with
272 we want to preserve. */
273 public static function preserveIndentation(): void {}
275 /** Multiline doc block with
276 * leading stars, as well as
278 * and we'd really like to preserve the Markdown list while getting rid of
279 * the other stars. */
280 public static function leadingStarsAndMDList(): void {}
283 * This method has many line breaks, which
285 * someone might use if they wanted
287 * to have separate paragraphs
291 public static function manyLineBreaks(): void {}
295 * Class doc block for a class whose constructor doesn't have a doc block.
297 final class DocBlockOnClassButNotConstructor {
298 public function __construct() {}
301 /* DocBlockBase: class doc block. */
303 /* DocBlockBase: constructor doc block. */
304 public function __construct() {}
307 /* DocBlockDerived: extends a class with a constructor, but doesn't have one of
309 class DocBlockDerived extends DocBlockBase {}
312 let docblockCases = [
313 ("docblock.php", 7, 3), [
315 snippet
= "class DocBlock";
316 addendum
= ["Class doc block.\n\
323 pos
= pos_at (7, 3) (7, 10);
326 ("docblock.php", 7, 13), [
328 snippet
= "public static function doStuff(): void";
329 addendum
= ["Method doc block with double star."; "Full name: `DocBlock::doStuff`"];
330 pos
= pos_at (7, 13) (7, 19);
333 ("docblock.php", 9, 3), [
335 snippet
= "function queryDocBlocks(): void";
336 addendum
= ["Multiline\n\
339 pos
= pos_at (9, 3) (9, 16);
342 ("docblock.php", 11, 13), [
344 snippet
= "public static function preserveIndentation(): void";
345 addendum
= ["Multiline doc block with
348 we want to preserve."; "Full name: `DocBlock::preserveIndentation`"];
349 pos
= pos_at (11, 13) (11, 31);
352 ("docblock.php", 13, 13), [
354 snippet
= "public static function leadingStarsAndMDList(): void";
355 addendum
= ["Multiline doc block with
356 leading stars, as well as
358 and we'd really like to preserve the Markdown list while getting rid of
359 the other stars."; "Full name: `DocBlock::leadingStarsAndMDList`"];
360 pos
= pos_at (13, 13) (13, 33);
363 ("docblock.php", 15, 13), [
365 snippet
= "public static function manyLineBreaks(): void";
368 This method has many line breaks, which\n\
370 someone might use if they wanted\n\
372 to have separate paragraphs\n\
375 "Full name: `DocBlock::manyLineBreaks`"];
376 pos
= pos_at (15, 13) (15, 26);
379 ("docblock.php", 17, 12), [
382 "public function __construct(): _";
383 (* This is because we set `last_line` to 0 in Docblock_finder, but we
384 can't get a proper `last_line` value without generating a TAST of the
385 file that contains this class. I'll be fixing this in a later diff.
388 "\nClass doc block for a class whose constructor doesn't have a doc block.\n";
389 "Full name: `DocBlockOnClassButNotConstructor::__construct`";
391 pos
= pos_at (17, 8) (17, 45);
394 ("docblock.php", 21, 28), [
396 snippet
= "DocBlockBase";
397 addendum
= ["DocBlockBase: class doc block."];
398 pos
= pos_at (21, 28) (21, 39);
401 ("docblock.php", 23, 12), [
403 snippet
= "public function __construct(): _";
405 "DocBlockBase: constructor doc block.";
406 "Full name: `DocBlockBase::__construct`";
408 pos
= pos_at (23, 8) (23, 25);
411 ("docblock.php", 25, 14), [
413 snippet
= "public function __construct(): _";
415 "DocBlockBase: constructor doc block.";
416 "Full name: `DocBlockBase::__construct`";
418 pos
= pos_at (25, 10) (25, 30);
423 let special_cases = "<?hh // strict
424 function special_cases(): void {
425 idx(array(1, 2, 3), 1);
430 let special_cases_cases = [
431 ("special_cases.php", 3, 3), [
435 ?KeyedContainer<int, "^
"
436 ?int> $collection, "^
"
440 pos
= pos_at (3, 3) (3, 24);
445 let bounded_generic_fun = "<?hh // strict
446 abstract class Base {}
447 final class C extends Base {}
448 function bounded_generic_fun<T as Base>(T $x): void {
451 if ($x instanceof C) {
461 let bounded_generic_fun_cases = [
462 ("bounded_generic_fun.php", 5, 3), [{
463 snippet
= "T\nwhere T as Base";
465 pos
= pos_at (5, 3) (5, 4);
467 ("bounded_generic_fun.php", 7, 7), [{
468 snippet
= "T\nwhere T as Base";
470 pos
= pos_at (7, 7) (7, 8);
472 ("bounded_generic_fun.php", 9, 5), [{
475 pos
= pos_at (9, 5) (9, 6);
477 ("bounded_generic_fun.php", 12, 3), [{
478 snippet
= "(C | T)\nwhere T as Base";
480 pos
= pos_at (12, 3) (12, 4);
485 "class_members.php", class_members;
486 "classname_call.php", classname_call;
487 "chained_calls.php", chained_calls;
488 "classname_variable.php", classname_variable;
489 "docblock.php", docblock;
490 "special_cases.php", special_cases;
491 "bounded_generic_fun.php", bounded_generic_fun;
497 @ class_members_cases
498 @ classname_call_cases
499 @ chained_calls_cases
500 @ classname_variable_cases
501 @ bounded_generic_fun_cases
506 ~hhi_files
:(Hhi.get_raw_hhi_contents
() |> Array.to_list
)
508 let env = Test.setup_disk
env files in
510 Test.assert_no_errors
env;
513 List.filter_map
cases ~f
:begin fun ((file
, line
, col
), expectedHover
) ->
514 let list_to_string hover_list
=
515 let string_list = hover_list
|> List.map ~f
:HoverService.string_of_result
in
516 let inner = match string_list |> List.reduce ~f
:(fun a b
-> a ^
"; " ^ b
) with
520 Printf.sprintf
"%s:%d:%d: [%s]" file line col
inner
522 let fn = ServerUtils.FileName
("/" ^ file
) in
523 let hover = ServerHover.go
env (fn, line
, col
) in
524 let expected = list_to_string expectedHover
in
525 let actual = list_to_string hover in
526 if expected <> actual then Some
(expected, actual) else None
528 match failed_cases with
531 let display_case (expected, actual) =
532 Printf.sprintf
"Expected:\n%s\nGot:\n%s" expected actual
534 Test.fail
@@ String.concat
"\n\n" (List.map ~f
:display_case cases)