Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/FragmentAutocomplete.test.cpp
2723 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
3
#include "Luau/FragmentAutocomplete.h"
4
#include "Fixture.h"
5
#include "Luau/Ast.h"
6
#include "Luau/AstQuery.h"
7
#include "Luau/Autocomplete.h"
8
#include "Luau/BuiltinDefinitions.h"
9
#include "Luau/Common.h"
10
#include "Luau/FileResolver.h"
11
#include "Luau/Frontend.h"
12
#include "Luau/AutocompleteTypes.h"
13
#include "Luau/ToString.h"
14
#include "Luau/Type.h"
15
#include "ScopedFlags.h"
16
17
#include <ctime>
18
#include <memory>
19
#include <optional>
20
21
using namespace Luau;
22
23
LUAU_FASTINT(LuauParseErrorLimit)
24
25
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
26
LUAU_FASTFLAG(LuauAutocompleteFunctionCallArgTails2)
27
LUAU_FASTFLAG(DebugLuauForceOldSolver)
28
LUAU_FASTFLAG(LuauReplacerRespectsReboundGenerics)
29
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
30
LUAU_FASTFLAG(LuauUnifier2HandleMismatchedPacks2)
31
32
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
33
{
34
return std::nullopt;
35
}
36
37
static FrontendOptions getOptions()
38
{
39
FrontendOptions options;
40
options.retainFullTypeGraphs = true;
41
42
if (FFlag::DebugLuauForceOldSolver)
43
options.forAutocomplete = true;
44
45
options.runLintChecks = false;
46
47
return options;
48
}
49
50
static ModuleResolver& getModuleResolver(Frontend& frontend)
51
{
52
return !FFlag::DebugLuauForceOldSolver ? frontend.moduleResolver : frontend.moduleResolverForAutocomplete;
53
}
54
55
template<class BaseType>
56
struct FragmentAutocompleteFixtureImpl : BaseType
57
{
58
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
59
60
FragmentAutocompleteFixtureImpl()
61
: BaseType(true)
62
{
63
}
64
65
CheckResult checkWithOptions(const std::string& source)
66
{
67
return this->check(source, getOptions());
68
}
69
70
std::string cleanMarkers(const std::string& source)
71
{
72
markerPosition.clear();
73
std::string filteredSource;
74
filteredSource.reserve(source.size());
75
76
Position curPos(0, 0);
77
char prevChar{};
78
for (char c : source)
79
{
80
if (prevChar == '@')
81
{
82
LUAU_ASSERT("Illegal marker character" && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')));
83
LUAU_ASSERT("Duplicate marker found" && markerPosition.count(c) == 0);
84
markerPosition.insert(std::pair{c, curPos});
85
}
86
else if (c == '@')
87
{
88
// skip the '@' character
89
if (prevChar == '\\')
90
{
91
// escaped @, prevent prevChar to be equal to '@' on next loop
92
c = '\0';
93
// replace escaping '\' with '@'
94
filteredSource.back() = '@';
95
}
96
}
97
else
98
{
99
filteredSource.push_back(c);
100
if (c == '\n')
101
{
102
curPos.line++;
103
curPos.column = 0;
104
}
105
else
106
{
107
curPos.column++;
108
}
109
}
110
prevChar = c;
111
}
112
LUAU_ASSERT("Digit expected after @ symbol" && prevChar != '@');
113
114
return filteredSource;
115
}
116
117
ParseResult parseHelper_(SourceModule& source, std::string document)
118
{
119
ParseOptions parseOptions;
120
parseOptions.captureComments = true;
121
ParseResult parseResult = Parser::parse(document.c_str(), document.length(), *source.names, *source.allocator, parseOptions);
122
return parseResult;
123
}
124
125
ParseResult parseHelper(std::string document)
126
{
127
SourceModule& source = getSource();
128
return parseHelper_(source, document);
129
}
130
131
FragmentAutocompleteAncestryResult runAutocompleteVisitor(const std::string& source, const Position& cursorPos)
132
{
133
ParseResult p = this->tryParse(source); // We don't care about parsing incomplete asts
134
REQUIRE(p.root);
135
return findAncestryForFragmentParse(p.root, cursorPos, p.root);
136
}
137
138
FragmentRegion getAutocompleteRegion(const std::string source, const Position& cursorPos)
139
{
140
ParseResult p = parseHelper(source);
141
return Luau::getFragmentRegion(p.root, cursorPos);
142
}
143
144
std::optional<FragmentParseResult> parseFragment(
145
const std::string& document,
146
const Position& cursorPos,
147
std::optional<Position> fragmentEndPosition = std::nullopt
148
)
149
{
150
ParseResult p = parseHelper(document);
151
ModulePtr module = this->getMainModule(getOptions().forAutocomplete);
152
std::string_view srcString = document;
153
return Luau::parseFragment(module->root, p.root, module->names.get(), srcString, cursorPos, fragmentEndPosition);
154
}
155
156
CheckResult checkOldSolver(const std::string& source)
157
{
158
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
159
return this->check(Mode::Strict, source, getOptions());
160
}
161
162
FragmentTypeCheckResult checkFragment(
163
const std::string& document,
164
const Position& cursorPos,
165
std::optional<Position> fragmentEndPosition = std::nullopt
166
)
167
{
168
ParseResult p = parseHelper(document);
169
auto [_, result] = Luau::typecheckFragment(this->getFrontend(), "MainModule", cursorPos, getOptions(), document, fragmentEndPosition, p.root);
170
return result;
171
}
172
173
FragmentAutocompleteStatusResult autocompleteFragment(
174
const std::string& document,
175
Position cursorPos,
176
std::optional<Position> fragmentEndPosition = std::nullopt
177
)
178
{
179
ParseOptions parseOptions;
180
parseOptions.captureComments = true;
181
ParseResult parseResult = parseHelper(document);
182
FrontendOptions options = getOptions();
183
FragmentContext context{document, parseResult, options, fragmentEndPosition};
184
return Luau::tryFragmentAutocomplete(this->getFrontend(), "MainModule", cursorPos, context, nullCallback);
185
}
186
187
void autocompleteFragmentInNewSolver(
188
const std::string& document,
189
const std::string& updated,
190
char marker,
191
std::function<void(FragmentAutocompleteStatusResult& result)> assertions,
192
std::optional<Position> fragmentEndPosition = std::nullopt
193
)
194
{
195
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
196
197
std::string cleanDocument = cleanMarkers(document);
198
std::string cleanUpdated = cleanMarkers(updated);
199
Position cursorPos = getPosition(marker);
200
201
this->getFrontend().setLuauSolverMode(SolverMode::New);
202
this->check(document, getOptions());
203
204
FragmentAutocompleteStatusResult result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition);
205
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
206
assertions(result);
207
}
208
209
void autocompleteFragmentInOldSolver(
210
const std::string& document,
211
const std::string& updated,
212
char marker,
213
std::function<void(FragmentAutocompleteStatusResult& result)> assertions,
214
std::optional<Position> fragmentEndPosition = std::nullopt
215
)
216
{
217
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
218
219
std::string cleanDocument = cleanMarkers(document);
220
std::string cleanUpdated = cleanMarkers(updated);
221
Position cursorPos = getPosition(marker);
222
223
this->getFrontend().setLuauSolverMode(SolverMode::Old);
224
this->check(document, getOptions());
225
226
FragmentAutocompleteStatusResult result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition);
227
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
228
assertions(result);
229
}
230
231
void autocompleteFragmentInBothSolvers(
232
const std::string& document,
233
const std::string& updated,
234
char marker,
235
std::function<void(FragmentAutocompleteStatusResult& result)> assertions,
236
std::optional<Position> fragmentEndPosition = std::nullopt
237
)
238
{
239
std::string cleanDocument = cleanMarkers(document);
240
std::string cleanUpdated = cleanMarkers(updated);
241
Position cursorPos = getPosition(marker);
242
243
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
244
this->getFrontend().setLuauSolverMode(SolverMode::New);
245
this->check(cleanDocument, getOptions());
246
247
FragmentAutocompleteStatusResult result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition);
248
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
249
assertions(result);
250
251
ScopedFastFlag _{FFlag::DebugLuauForceOldSolver, true};
252
this->getFrontend().setLuauSolverMode(SolverMode::Old);
253
this->check(cleanDocument, getOptions());
254
255
result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition);
256
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
257
assertions(result);
258
}
259
260
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragmentForModule(
261
const ModuleName& module,
262
const std::string& document,
263
Position cursorPos,
264
std::optional<Position> fragmentEndPosition = std::nullopt
265
)
266
{
267
ParseResult pr = parseHelper(document);
268
return Luau::typecheckFragment(this->getFrontend(), module, cursorPos, getOptions(), document, fragmentEndPosition, pr.root);
269
}
270
271
FragmentAutocompleteStatusResult autocompleteFragmentForModule(
272
const ModuleName& module,
273
const std::string& document,
274
Position cursorPos,
275
std::optional<Position> fragmentEndPosition = std::nullopt
276
)
277
{
278
ParseOptions parseOptions;
279
parseOptions.captureComments = true;
280
ParseResult parseResult = parseHelper(document);
281
FrontendOptions options;
282
FragmentContext context{document, parseResult, options, fragmentEndPosition};
283
return Luau::tryFragmentAutocomplete(this->getFrontend().module, cursorPos, context, nullCallback);
284
}
285
286
SourceModule& getSource()
287
{
288
source = std::make_unique<SourceModule>();
289
return *source;
290
}
291
292
const Position& getPosition(char marker) const
293
{
294
auto i = markerPosition.find(marker);
295
LUAU_ASSERT(i != markerPosition.end());
296
return i->second;
297
}
298
299
// Maps a marker character (0-9 inclusive) to a position in the source code.
300
std::map<char, Position> markerPosition;
301
302
private:
303
std::unique_ptr<SourceModule> source = std::make_unique<SourceModule>();
304
};
305
306
struct FragmentAutocompleteFixture : FragmentAutocompleteFixtureImpl<Fixture>
307
{
308
FragmentAutocompleteFixture()
309
: FragmentAutocompleteFixtureImpl<Fixture>()
310
{
311
addGlobalBinding(getFrontend().globals, "table", Binding{getBuiltins()->anyType});
312
addGlobalBinding(getFrontend().globals, "math", Binding{getBuiltins()->anyType});
313
addGlobalBinding(getFrontend().globalsForAutocomplete, "table", Binding{getBuiltins()->anyType});
314
addGlobalBinding(getFrontend().globalsForAutocomplete, "math", Binding{getBuiltins()->anyType});
315
}
316
};
317
318
struct FragmentAutocompleteBuiltinsFixture : FragmentAutocompleteFixtureImpl<BuiltinsFixture>
319
{
320
FragmentAutocompleteBuiltinsFixture()
321
: FragmentAutocompleteFixtureImpl<BuiltinsFixture>()
322
{
323
}
324
325
Frontend& getFrontend() override
326
{
327
if (frontend)
328
return *frontend;
329
Frontend& f = BuiltinsFixture::getFrontend();
330
Luau::unfreeze(f.globals.globalTypes);
331
Luau::unfreeze(f.globalsForAutocomplete.globalTypes);
332
const std::string fakeVecDecl = R"(
333
declare class FakeVec
334
function dot(self, x: FakeVec) : FakeVec
335
zero : FakeVec
336
end
337
)";
338
// The old solver always performs a strict mode check and populates the module resolver and globals
339
// for autocomplete.
340
// The new solver just populates the globals and the moduleResolver.
341
// Because these tests run in both the old solver and the new solver, and the test suite
342
// now picks the module resolver as appropriate in order to better mimic the studio code path,
343
// we have to load the definition file into both the 'globals'/'resolver' and the equivalent
344
// 'for autocomplete'.
345
loadDefinition(fakeVecDecl);
346
loadDefinition(fakeVecDecl, /* For Autocomplete Module */ true);
347
348
addGlobalBinding(getFrontend().globals, "game", Binding{getBuiltins()->anyType});
349
addGlobalBinding(getFrontend().globalsForAutocomplete, "game", Binding{getBuiltins()->anyType});
350
Luau::freeze(f.globals.globalTypes);
351
Luau::freeze(f.globalsForAutocomplete.globalTypes);
352
353
return *frontend;
354
}
355
};
356
357
TEST_SUITE_BEGIN("FragmentSelectionSpecTests");
358
359
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "just_two_locals")
360
{
361
auto region = getAutocompleteRegion(
362
R"(
363
local x = 4
364
local y = 5
365
)",
366
{2, 11}
367
);
368
369
CHECK_EQ(Location{{2, 0}, {2, 11}}, region.fragmentLocation);
370
REQUIRE(region.parentBlock);
371
REQUIRE(region.nearestStatement);
372
CHECK(region.nearestStatement->as<AstStatLocal>());
373
}
374
375
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "singleline_call")
376
{
377
auto region = getAutocompleteRegion(
378
R"(
379
abc("foo")
380
)",
381
{1, 10}
382
);
383
384
CHECK_EQ(Location{{1, 0}, {1, 10}}, region.fragmentLocation);
385
REQUIRE(region.parentBlock);
386
REQUIRE(region.nearestStatement);
387
CHECK(region.nearestStatement->as<AstStatExpr>());
388
}
389
390
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "midway_multiline_call")
391
{
392
auto region = getAutocompleteRegion(
393
R"(
394
abc(
395
"foo"
396
)
397
)",
398
{2, 4}
399
);
400
401
CHECK_EQ(Location{{1, 0}, {2, 4}}, region.fragmentLocation);
402
REQUIRE(region.parentBlock);
403
REQUIRE(region.nearestStatement);
404
CHECK(region.nearestStatement->as<AstStatExpr>());
405
}
406
407
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "end_multiline_call")
408
{
409
auto region = getAutocompleteRegion(
410
R"(
411
abc(
412
"foo"
413
)
414
)",
415
{3, 1}
416
);
417
418
CHECK_EQ(Location{{1, 0}, {3, 1}}, region.fragmentLocation);
419
REQUIRE(region.parentBlock);
420
REQUIRE(region.nearestStatement);
421
CHECK(region.nearestStatement->as<AstStatExpr>());
422
}
423
424
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "midway_through_call")
425
{
426
auto region = getAutocompleteRegion(
427
R"(
428
abc("foo")
429
)",
430
{1, 6}
431
);
432
433
CHECK_EQ(Location{{1, 0}, {1, 6}}, region.fragmentLocation);
434
REQUIRE(region.parentBlock);
435
REQUIRE(region.nearestStatement);
436
CHECK(region.nearestStatement->as<AstStatExpr>());
437
}
438
439
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inside_incomplete_do")
440
{
441
auto region = getAutocompleteRegion(
442
R"(
443
local x = 4
444
do
445
)",
446
{2, 2}
447
);
448
449
CHECK_EQ(Location{{2, 2}, {2, 2}}, region.fragmentLocation);
450
REQUIRE(region.parentBlock);
451
CHECK(region.nearestStatement->as<AstStatBlock>());
452
}
453
454
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "end_of_do")
455
{
456
auto region = getAutocompleteRegion(
457
R"(
458
local x = 4
459
do
460
end
461
)",
462
{3, 3}
463
);
464
465
CHECK_EQ(Location{{3, 3}, {3, 3}}, region.fragmentLocation);
466
REQUIRE(region.parentBlock);
467
CHECK(region.nearestStatement->as<AstStatBlock>());
468
}
469
470
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inside_do")
471
{
472
auto region = getAutocompleteRegion(
473
R"(
474
local x = 4
475
do
476
477
end
478
)",
479
{3, 3}
480
);
481
482
CHECK_EQ(Location{{3, 3}, {3, 3}}, region.fragmentLocation);
483
REQUIRE(region.parentBlock);
484
CHECK(region.nearestStatement->as<AstStatBlock>());
485
}
486
487
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_statement_inside_do")
488
{
489
auto region = getAutocompleteRegion(
490
R"(
491
local x = 4
492
do
493
local x =
494
end
495
)",
496
{3, 13}
497
);
498
499
CHECK_EQ(Location{{3, 4}, {3, 13}}, region.fragmentLocation);
500
REQUIRE(region.parentBlock);
501
CHECK(region.nearestStatement->as<AstStatLocal>());
502
}
503
504
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_statement_after_do")
505
{
506
auto region = getAutocompleteRegion(
507
R"(
508
local x = 4
509
do
510
511
end
512
local x =
513
)",
514
{5, 9}
515
);
516
517
CHECK_EQ(Location{{5, 0}, {5, 9}}, region.fragmentLocation);
518
REQUIRE(region.parentBlock);
519
CHECK(region.nearestStatement->as<AstStatLocal>());
520
}
521
522
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "before_func")
523
{
524
auto region = getAutocompleteRegion(
525
R"(
526
function f()
527
end
528
)",
529
{1, 0}
530
);
531
CHECK_EQ(Location{{1, 0}, {1, 0}}, region.fragmentLocation);
532
REQUIRE(region.parentBlock);
533
CHECK(region.nearestStatement->as<AstStatFunction>());
534
}
535
536
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "after_func_same_line")
537
{
538
auto region = getAutocompleteRegion(
539
R"(
540
function f()
541
end
542
)",
543
{2, 3}
544
);
545
CHECK_EQ(Location{{2, 3}, {2, 3}}, region.fragmentLocation);
546
REQUIRE(region.parentBlock);
547
CHECK(region.nearestStatement->as<AstStatFunction>());
548
}
549
550
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "after_func_new_line")
551
{
552
auto region = getAutocompleteRegion(
553
R"(
554
function f()
555
end
556
557
)",
558
{3, 0}
559
);
560
CHECK_EQ(Location{{3, 0}, {3, 0}}, region.fragmentLocation);
561
REQUIRE(region.parentBlock);
562
CHECK(region.nearestStatement->as<AstStatFunction>());
563
}
564
565
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "while_writing_func")
566
{
567
auto region = getAutocompleteRegion(
568
R"(
569
function f(arg1,
570
)",
571
{1, 17}
572
);
573
CHECK_EQ(Location{{1, 0}, {1, 17}}, region.fragmentLocation);
574
REQUIRE(region.parentBlock);
575
CHECK(region.nearestStatement->as<AstStatFunction>());
576
}
577
578
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_func_annotation")
579
{
580
auto region = getAutocompleteRegion(
581
R"(
582
function f(arg1 : T
583
)",
584
{1, 19}
585
);
586
CHECK_EQ(Location{{1, 0}, {1, 19}}, region.fragmentLocation);
587
REQUIRE(region.parentBlock);
588
CHECK(region.nearestStatement->as<AstStatFunction>());
589
}
590
591
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_func_return")
592
{
593
auto region = getAutocompleteRegion(
594
R"(
595
function f(arg1 : T) :
596
)",
597
{1, 22}
598
);
599
CHECK_EQ(Location{{1, 0}, {1, 22}}, region.fragmentLocation);
600
REQUIRE(region.parentBlock);
601
CHECK(region.nearestStatement->as<AstStatFunction>());
602
}
603
604
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_func_return_pack")
605
{
606
auto region = getAutocompleteRegion(
607
R"(
608
function f(arg1 : T) : T...
609
)",
610
{1, 27}
611
);
612
CHECK_EQ(Location{{1, 0}, {1, 27}}, region.fragmentLocation);
613
REQUIRE(region.parentBlock);
614
CHECK(region.nearestStatement->as<AstStatFunction>());
615
}
616
617
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "before_local_func")
618
{
619
auto region = getAutocompleteRegion(
620
R"(
621
local function f()
622
end
623
)",
624
{1, 0}
625
);
626
CHECK_EQ(Location{{1, 0}, {1, 0}}, region.fragmentLocation);
627
REQUIRE(region.parentBlock);
628
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
629
}
630
631
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "after_local_func_same_line")
632
{
633
auto region = getAutocompleteRegion(
634
R"(
635
local function f()
636
end
637
)",
638
{2, 3}
639
);
640
CHECK_EQ(Location{{2, 3}, {2, 3}}, region.fragmentLocation);
641
REQUIRE(region.parentBlock);
642
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
643
}
644
645
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "after_local_func_new_line")
646
{
647
auto region = getAutocompleteRegion(
648
R"(
649
local function f()
650
end
651
652
)",
653
{3, 0}
654
);
655
CHECK_EQ(Location{{3, 0}, {3, 0}}, region.fragmentLocation);
656
REQUIRE(region.parentBlock);
657
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
658
}
659
660
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "while_writing_local_func")
661
{
662
auto region = getAutocompleteRegion(
663
R"(
664
local function f(arg1,
665
)",
666
{1, 22}
667
);
668
CHECK_EQ(Location{{1, 0}, {1, 22}}, region.fragmentLocation);
669
REQUIRE(region.parentBlock);
670
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
671
}
672
673
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_local_func_annotation")
674
{
675
auto region = getAutocompleteRegion(
676
R"(
677
local function f(arg1 : T
678
)",
679
{1, 25}
680
);
681
CHECK_EQ(Location{{1, 0}, {1, 25}}, region.fragmentLocation);
682
REQUIRE(region.parentBlock);
683
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
684
}
685
686
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_local_func_return")
687
{
688
auto region = getAutocompleteRegion(
689
R"(
690
local function f(arg1 : T) :
691
)",
692
{1, 28}
693
);
694
CHECK_EQ(Location{{1, 0}, {1, 28}}, region.fragmentLocation);
695
REQUIRE(region.parentBlock);
696
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
697
}
698
699
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_local_func_return_pack")
700
{
701
auto region = getAutocompleteRegion(
702
R"(
703
local function f(arg1 : T) : T...
704
)",
705
{1, 33}
706
);
707
CHECK_EQ(Location{{1, 0}, {1, 33}}, region.fragmentLocation);
708
REQUIRE(region.parentBlock);
709
CHECK(region.nearestStatement->as<AstStatLocalFunction>());
710
}
711
712
713
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "single_line_local_and_annot")
714
{
715
auto region = getAutocompleteRegion(
716
R"(
717
type Part = {x : number}
718
local part : Part = {x = 3}; pa
719
)",
720
{2, 32}
721
);
722
CHECK_EQ(Location{{2, 29}, {2, 32}}, region.fragmentLocation);
723
REQUIRE(region.parentBlock);
724
CHECK(region.nearestStatement->as<AstStatError>());
725
}
726
727
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_while_in_condition")
728
{
729
auto region = getAutocompleteRegion(
730
R"(
731
while t
732
)",
733
Position{1, 7}
734
);
735
736
CHECK_EQ(Location{{1, 0}, {1, 7}}, region.fragmentLocation);
737
REQUIRE(region.parentBlock);
738
CHECK(region.nearestStatement->as<AstStatWhile>());
739
}
740
741
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "while_inside_condition_same_line")
742
{
743
auto region = getAutocompleteRegion(
744
R"(
745
while true do
746
end
747
)",
748
Position{1, 13}
749
);
750
751
CHECK_EQ(Location{{1, 13}, {1, 13}}, region.fragmentLocation);
752
REQUIRE(region.parentBlock);
753
CHECK(region.nearestStatement->as<AstStatWhile>());
754
}
755
756
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_numeric_in_condition")
757
{
758
auto region = getAutocompleteRegion(
759
R"(
760
for c = 1,3
761
)",
762
Position{1, 11}
763
);
764
765
CHECK_EQ(Location{{1, 10}, {1, 11}}, region.fragmentLocation);
766
REQUIRE(region.parentBlock);
767
CHECK(region.nearestStatement->as<AstStatFor>());
768
}
769
770
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_numeric_in_body")
771
{
772
auto region = getAutocompleteRegion(
773
R"(
774
for c = 1,3 do
775
)",
776
Position{1, 14}
777
);
778
779
CHECK_EQ(Location{{1, 14}, {1, 14}}, region.fragmentLocation);
780
REQUIRE(region.parentBlock);
781
CHECK(region.nearestStatement->as<AstStatFor>());
782
}
783
784
785
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_in_in_condition_1")
786
{
787
auto region = getAutocompleteRegion(
788
R"(
789
for i,v in {1,2,3}
790
)",
791
Position{1, 18}
792
);
793
794
CHECK_EQ(Location{{1, 0}, {1, 18}}, region.fragmentLocation);
795
REQUIRE(region.parentBlock);
796
CHECK(region.nearestStatement->as<AstStatForIn>());
797
}
798
799
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_in_in_condition_2")
800
{
801
auto region = getAutocompleteRegion(
802
R"(
803
for i,v in
804
)",
805
Position{1, 10}
806
);
807
808
CHECK_EQ(Location{{1, 0}, {1, 10}}, region.fragmentLocation);
809
REQUIRE(region.parentBlock);
810
CHECK(region.nearestStatement->as<AstStatForIn>());
811
}
812
813
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_in_in_condition_3")
814
{
815
auto region = getAutocompleteRegion(
816
R"(
817
for i,
818
)",
819
Position{1, 6}
820
);
821
822
CHECK_EQ(Location{{1, 0}, {1, 6}}, region.fragmentLocation);
823
REQUIRE(region.parentBlock);
824
CHECK(region.nearestStatement->as<AstStatForIn>());
825
}
826
827
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_in_in_body")
828
{
829
auto region = getAutocompleteRegion(
830
R"(
831
for i,v in {1,2,3} do
832
)",
833
Position{1, 21}
834
);
835
836
CHECK_EQ(Location{{1, 21}, {1, 21}}, region.fragmentLocation);
837
REQUIRE(region.parentBlock);
838
CHECK(region.nearestStatement->as<AstStatForIn>());
839
}
840
841
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial")
842
{
843
auto region = getAutocompleteRegion(
844
R"(
845
if)",
846
Position{1, 2}
847
);
848
CHECK_EQ(Location{{1, 2}, {1, 2}}, region.fragmentLocation);
849
REQUIRE(region.parentBlock);
850
CHECK(region.nearestStatement->as<AstStatIf>());
851
}
852
853
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_in_condition_at")
854
{
855
auto region = getAutocompleteRegion(
856
R"(
857
if true
858
)",
859
Position{1, 7}
860
);
861
CHECK_EQ(Location{{1, 3}, {1, 7}}, region.fragmentLocation);
862
REQUIRE(region.parentBlock);
863
CHECK(region.nearestStatement->as<AstStatIf>());
864
}
865
866
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_in_condition_after")
867
{
868
auto region = getAutocompleteRegion(
869
R"(
870
if true
871
)",
872
Position{1, 8}
873
);
874
CHECK_EQ(Location{{1, 3}, {1, 8}}, region.fragmentLocation);
875
REQUIRE(region.parentBlock);
876
CHECK(region.nearestStatement->as<AstStatIf>());
877
}
878
879
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_after_condition")
880
{
881
auto region = getAutocompleteRegion(
882
R"(
883
if true then
884
)",
885
Position{1, 12}
886
);
887
CHECK_EQ(Location{{1, 12}, {1, 12}}, region.fragmentLocation);
888
REQUIRE(region.parentBlock);
889
CHECK(region.nearestStatement->as<AstStatIf>());
890
}
891
892
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_new_line")
893
{
894
auto region = getAutocompleteRegion(
895
R"(
896
if true then
897
898
)",
899
Position{2, 0}
900
);
901
CHECK_EQ(Location{{2, 0}, {2, 0}}, region.fragmentLocation);
902
REQUIRE(region.parentBlock);
903
CHECK(region.nearestStatement->as<AstStatIf>());
904
}
905
906
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_complete_inside_scope_line")
907
{
908
auto region = getAutocompleteRegion(
909
R"(
910
if true then
911
local x =
912
end
913
914
)",
915
Position{2, 13}
916
);
917
CHECK_EQ(Location{{2, 4}, {2, 13}}, region.fragmentLocation);
918
REQUIRE(region.parentBlock);
919
CHECK(region.nearestStatement->as<AstStatLocal>());
920
}
921
922
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if")
923
{
924
auto region = getAutocompleteRegion(
925
R"(
926
if true then
927
elseif
928
end
929
930
)",
931
Position{2, 8}
932
);
933
CHECK_EQ(Location{{2, 8}, {2, 8}}, region.fragmentLocation);
934
REQUIRE(region.parentBlock);
935
CHECK(region.nearestStatement->as<AstStatIf>());
936
}
937
938
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_no_end")
939
{
940
auto region = getAutocompleteRegion(
941
R"(
942
if true then
943
elseif
944
)",
945
Position{2, 8}
946
);
947
CHECK_EQ(Location{{2, 8}, {2, 8}}, region.fragmentLocation);
948
REQUIRE(region.parentBlock);
949
CHECK(region.nearestStatement->as<AstStatIf>());
950
}
951
952
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_after_then")
953
{
954
auto region = getAutocompleteRegion(
955
R"(
956
if true then
957
elseif false then
958
end
959
960
)",
961
Position{2, 17}
962
);
963
CHECK_EQ(Location{{2, 17}, {2, 17}}, region.fragmentLocation);
964
REQUIRE(region.parentBlock);
965
CHECK(region.nearestStatement->as<AstStatIf>());
966
}
967
968
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_after_then_new_line")
969
{
970
auto region = getAutocompleteRegion(
971
R"(
972
if true then
973
elseif false then
974
975
end
976
977
)",
978
Position{3, 0}
979
);
980
CHECK_EQ(Location{{3, 0}, {3, 0}}, region.fragmentLocation);
981
REQUIRE(region.parentBlock);
982
CHECK(region.nearestStatement->as<AstStatIf>());
983
}
984
985
986
TEST_SUITE_END();
987
988
// NOLINTBEGIN(bugprone-unchecked-optional-access)
989
TEST_SUITE_BEGIN("FragmentAutocompleteTraversalTests");
990
991
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "just_two_locals")
992
{
993
auto result = runAutocompleteVisitor(
994
R"(
995
local x = 4
996
local y = 5
997
)",
998
{2, 11}
999
);
1000
1001
CHECK_EQ(3, result.ancestry.size());
1002
CHECK_EQ(1, result.localStack.size());
1003
CHECK_EQ(result.localMap.size(), result.localStack.size());
1004
REQUIRE(result.nearestStatement);
1005
1006
AstStatLocal* local = result.nearestStatement->as<AstStatLocal>();
1007
REQUIRE(local);
1008
CHECK(1 == local->vars.size);
1009
CHECK_EQ("y", std::string(local->vars.data[0]->name.value));
1010
}
1011
1012
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "cursor_within_scope_tracks_locals_from_previous_scope")
1013
{
1014
auto result = runAutocompleteVisitor(
1015
R"(
1016
local x = 4
1017
local y = 5
1018
if x == 4 then
1019
local e = y
1020
end
1021
)",
1022
{4, 15}
1023
);
1024
1025
CHECK_EQ(5, result.ancestry.size());
1026
CHECK_EQ(2, result.localStack.size());
1027
CHECK_EQ(result.localMap.size(), result.localStack.size());
1028
REQUIRE(result.nearestStatement);
1029
CHECK_EQ("y", std::string(result.localStack.back()->name.value));
1030
1031
AstStatLocal* local = result.nearestStatement->as<AstStatLocal>();
1032
REQUIRE(local);
1033
CHECK(1 == local->vars.size);
1034
CHECK_EQ("e", std::string(local->vars.data[0]->name.value));
1035
}
1036
1037
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "cursor_that_comes_later_shouldnt_capture_locals_in_unavailable_scope")
1038
{
1039
auto result = runAutocompleteVisitor(
1040
R"(
1041
local x = 4
1042
local y = 5
1043
if x == 4 then
1044
local e = y
1045
end
1046
local z = x + x
1047
if y == 5 then
1048
local q = x + y + z
1049
end
1050
)",
1051
{8, 23}
1052
);
1053
1054
CHECK_EQ(6, result.ancestry.size());
1055
CHECK_EQ(3, result.localStack.size());
1056
CHECK_EQ(result.localMap.size(), result.localStack.size());
1057
REQUIRE(result.nearestStatement);
1058
CHECK_EQ("z", std::string(result.localStack.back()->name.value));
1059
1060
AstStatLocal* local = result.nearestStatement->as<AstStatLocal>();
1061
REQUIRE(local);
1062
CHECK(1 == local->vars.size);
1063
CHECK_EQ("q", std::string(local->vars.data[0]->name.value));
1064
}
1065
1066
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "nearest_enclosing_statement_can_be_non_local")
1067
{
1068
auto result = runAutocompleteVisitor(
1069
R"(
1070
local x = 4
1071
local y = 5
1072
if x == 4 then
1073
)",
1074
{3, 4}
1075
);
1076
1077
CHECK_EQ(4, result.ancestry.size());
1078
CHECK_EQ(2, result.localStack.size());
1079
CHECK_EQ(result.localMap.size(), result.localStack.size());
1080
REQUIRE(result.nearestStatement);
1081
CHECK_EQ("y", std::string(result.localStack.back()->name.value));
1082
1083
AstStatIf* ifS = result.nearestStatement->as<AstStatIf>();
1084
CHECK(ifS != nullptr);
1085
}
1086
1087
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_funcs_show_up_in_local_stack")
1088
{
1089
auto result = runAutocompleteVisitor(
1090
R"(
1091
local function foo() return 4 end
1092
local x = foo()
1093
local function bar() return x + foo() end
1094
)",
1095
{3, 32}
1096
);
1097
1098
CHECK_EQ(8, result.ancestry.size());
1099
CHECK_EQ(3, result.localStack.size());
1100
CHECK_EQ(result.localMap.size(), result.localStack.size());
1101
CHECK_EQ("bar", std::string(result.localStack.back()->name.value));
1102
auto returnSt = result.nearestStatement->as<AstStatReturn>();
1103
CHECK(returnSt != nullptr);
1104
}
1105
1106
TEST_SUITE_END();
1107
1108
1109
TEST_SUITE_BEGIN("FragmentAutocompleteParserTests");
1110
1111
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "empty_program_1")
1112
{
1113
checkWithOptions("");
1114
ScopedFastInt sfi{FInt::LuauParseErrorLimit, 1};
1115
auto fragment = parseFragment("", Position(0, 39));
1116
REQUIRE(fragment);
1117
CHECK(fragment->fragmentToParse == "");
1118
}
1119
1120
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "empty_program_2")
1121
{
1122
const std::string source = R"(
1123
1124
)";
1125
checkWithOptions(source);
1126
ScopedFastInt sfi{FInt::LuauParseErrorLimit, 1};
1127
auto fragment = parseFragment(source, Position(1, 39));
1128
REQUIRE(fragment);
1129
CHECK(fragment->fragmentToParse == "");
1130
}
1131
1132
1133
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "thrown_parse_error_leads_to_null_root")
1134
{
1135
checkWithOptions("type A = ");
1136
ScopedFastInt sfi{FInt::LuauParseErrorLimit, 1};
1137
auto fragment = parseFragment("type A = <>function<> more garbage here", Position(0, 39));
1138
CHECK(fragment == std::nullopt);
1139
}
1140
1141
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_initializer")
1142
{
1143
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1144
checkWithOptions("local a =");
1145
auto fragment = parseFragment("local a =", Position(0, 9));
1146
1147
REQUIRE(fragment.has_value());
1148
CHECK_EQ("local a =", fragment->fragmentToParse);
1149
CHECK_EQ(Location{Position{0, 0}, 9}, fragment->root->location);
1150
}
1151
1152
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "statement_in_empty_fragment_is_non_null")
1153
{
1154
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1155
auto res = checkWithOptions(R"(
1156
1157
)");
1158
1159
LUAU_REQUIRE_NO_ERRORS(res);
1160
1161
auto fragment = parseFragment(
1162
R"(
1163
1164
)",
1165
Position(1, 0)
1166
);
1167
REQUIRE(fragment.has_value());
1168
CHECK_EQ("", fragment->fragmentToParse);
1169
CHECK_EQ(1, fragment->ancestry.size());
1170
REQUIRE(fragment->root);
1171
CHECK_EQ(0, fragment->root->body.size);
1172
auto statBody = fragment->root->as<AstStatBlock>();
1173
CHECK(statBody != nullptr);
1174
}
1175
1176
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_complete_fragments")
1177
{
1178
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1179
auto res = checkWithOptions(
1180
R"(
1181
local x = 4
1182
local y = 5
1183
)"
1184
);
1185
1186
LUAU_REQUIRE_NO_ERRORS(res);
1187
1188
auto fragment = parseFragment(
1189
R"(
1190
local x = 4
1191
local y = 5
1192
local z = x + y
1193
)",
1194
Position{3, 15}
1195
);
1196
1197
REQUIRE(fragment.has_value());
1198
1199
CHECK_EQ(Location{Position{3, 0}, Position{3, 15}}, fragment->root->location);
1200
1201
CHECK_EQ("local z = x + y", fragment->fragmentToParse);
1202
CHECK_EQ(4, fragment->ancestry.size());
1203
REQUIRE(fragment->root);
1204
CHECK_EQ(1, fragment->root->body.size);
1205
auto stat = fragment->root->body.data[0]->as<AstStatLocal>();
1206
REQUIRE(stat);
1207
CHECK_EQ(1, stat->vars.size);
1208
CHECK_EQ(1, stat->values.size);
1209
CHECK_EQ("z", std::string(stat->vars.data[0]->name.value));
1210
1211
auto bin = stat->values.data[0]->as<AstExprBinary>();
1212
REQUIRE(bin);
1213
CHECK_EQ(AstExprBinary::Op::Add, bin->op);
1214
1215
auto lhs = bin->left->as<AstExprLocal>();
1216
auto rhs = bin->right->as<AstExprLocal>();
1217
REQUIRE(lhs);
1218
REQUIRE(rhs);
1219
CHECK_EQ("x", std::string(lhs->local->name.value));
1220
CHECK_EQ("y", std::string(rhs->local->name.value));
1221
}
1222
1223
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_fragments_in_line")
1224
{
1225
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1226
auto res = checkWithOptions(
1227
R"(
1228
local x = 4
1229
local y = 5
1230
)"
1231
);
1232
1233
LUAU_REQUIRE_NO_ERRORS(res);
1234
1235
auto fragment = parseFragment(
1236
R"(
1237
local x = 4
1238
local z = x + y
1239
local y = 5
1240
)",
1241
Position{2, 15}
1242
);
1243
1244
REQUIRE(fragment.has_value());
1245
1246
CHECK_EQ("local z = x + y", fragment->fragmentToParse);
1247
CHECK_EQ(4, fragment->ancestry.size());
1248
REQUIRE(fragment->root);
1249
CHECK_EQ(Location{Position{2, 0}, Position{2, 15}}, fragment->root->location);
1250
CHECK_EQ(1, fragment->root->body.size);
1251
auto stat = fragment->root->body.data[0]->as<AstStatLocal>();
1252
REQUIRE(stat);
1253
CHECK_EQ(1, stat->vars.size);
1254
CHECK_EQ(1, stat->values.size);
1255
CHECK_EQ("z", std::string(stat->vars.data[0]->name.value));
1256
1257
auto bin = stat->values.data[0]->as<AstExprBinary>();
1258
REQUIRE(bin);
1259
CHECK_EQ(AstExprBinary::Op::Add, bin->op);
1260
1261
auto lhs = bin->left->as<AstExprLocal>();
1262
auto rhs = bin->right->as<AstExprGlobal>();
1263
REQUIRE(lhs);
1264
REQUIRE(rhs);
1265
CHECK_EQ("x", std::string(lhs->local->name.value));
1266
CHECK_EQ("y", std::string(rhs->name.value));
1267
}
1268
1269
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_in_correct_scope")
1270
{
1271
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1272
checkWithOptions(R"(
1273
local myLocal = 4
1274
function abc()
1275
local myInnerLocal = 1
1276
1277
end
1278
)");
1279
1280
auto fragment = parseFragment(
1281
R"(
1282
local myLocal = 4
1283
function abc()
1284
local myInnerLocal = 1
1285
1286
end
1287
)",
1288
Position{6, 0}
1289
);
1290
1291
REQUIRE(fragment.has_value());
1292
1293
CHECK_EQ("", fragment->fragmentToParse);
1294
}
1295
1296
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_single_line_fragment_override")
1297
{
1298
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1299
auto res = checkWithOptions("function abc(foo: string) end");
1300
1301
LUAU_REQUIRE_NO_ERRORS(res);
1302
1303
auto callFragment = parseFragment(
1304
R"(function abc(foo: string) end
1305
abc("foo")
1306
abc("bar")
1307
)",
1308
Position{1, 6},
1309
Position{1, 10}
1310
);
1311
1312
REQUIRE(callFragment.has_value());
1313
1314
CHECK_EQ("abc(\"foo\")", callFragment->fragmentToParse);
1315
CHECK(callFragment->nearestStatement);
1316
CHECK(callFragment->nearestStatement->is<AstStatExpr>());
1317
1318
CHECK_GE(callFragment->ancestry.size(), 2);
1319
1320
AstNode* back = callFragment->ancestry.back();
1321
CHECK(back->is<AstExprConstantString>());
1322
CHECK_EQ(Position{1, 4}, back->location.begin);
1323
CHECK_EQ(Position{1, 9}, back->location.end);
1324
1325
AstNode* parent = callFragment->ancestry.rbegin()[1];
1326
CHECK(parent->is<AstExprCall>());
1327
CHECK_EQ(Position{1, 0}, parent->location.begin);
1328
CHECK_EQ(Position{1, 10}, parent->location.end);
1329
1330
1331
auto stringFragment = parseFragment(
1332
R"(function abc(foo: string) end
1333
abc("foo")
1334
abc("bar")
1335
)",
1336
Position{1, 6},
1337
Position{1, 9}
1338
);
1339
1340
REQUIRE(stringFragment.has_value());
1341
1342
CHECK_EQ("abc(\"foo\"", stringFragment->fragmentToParse);
1343
CHECK(stringFragment->nearestStatement);
1344
CHECK(stringFragment->nearestStatement->is<AstStatExpr>());
1345
1346
CHECK_GE(stringFragment->ancestry.size(), 1);
1347
1348
back = stringFragment->ancestry.back();
1349
1350
auto asString = back->as<AstExprConstantString>();
1351
CHECK(asString);
1352
1353
CHECK_EQ(Position{1, 4}, asString->location.begin);
1354
CHECK_EQ(Position{1, 9}, asString->location.end);
1355
CHECK_EQ("foo", std::string{asString->value.data});
1356
CHECK_EQ(AstExprConstantString::QuotedSimple, asString->quoteStyle);
1357
}
1358
1359
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_multi_line_fragment_override")
1360
{
1361
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1362
1363
auto res = checkWithOptions("function abc(foo: string) end");
1364
1365
LUAU_REQUIRE_NO_ERRORS(res);
1366
1367
auto fragment = parseFragment(
1368
R"(function abc(foo: string) end
1369
abc(
1370
"foo"
1371
)
1372
abc("bar")
1373
)",
1374
Position{2, 5},
1375
Position{3, 1}
1376
);
1377
1378
REQUIRE(fragment.has_value());
1379
1380
CHECK_EQ("abc(\n\"foo\"\n)", fragment->fragmentToParse);
1381
CHECK(fragment->nearestStatement);
1382
CHECK(fragment->nearestStatement->is<AstStatExpr>());
1383
1384
CHECK_GE(fragment->ancestry.size(), 2);
1385
1386
AstNode* back = fragment->ancestry.back();
1387
CHECK(back->is<AstExprConstantString>());
1388
CHECK_EQ(Position{2, 0}, back->location.begin);
1389
CHECK_EQ(Position{2, 5}, back->location.end);
1390
1391
AstNode* parent = fragment->ancestry.rbegin()[1];
1392
CHECK(parent->is<AstExprCall>());
1393
CHECK_EQ(Position{1, 0}, parent->location.begin);
1394
CHECK_EQ(Position{3, 1}, parent->location.end);
1395
}
1396
1397
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "respects_frontend_options")
1398
{
1399
DOES_NOT_PASS_NEW_SOLVER_GUARD();
1400
1401
std::string source = R"(
1402
local tbl = { abc = 1234}
1403
t
1404
)";
1405
fileResolver.source["game/A"] = source;
1406
1407
FrontendOptions opts;
1408
opts.forAutocomplete = true;
1409
getFrontend().setLuauSolverMode(!FFlag::DebugLuauForceOldSolver ? SolverMode::New : SolverMode::Old);
1410
getFrontend().check("game/A", opts);
1411
CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr);
1412
CHECK_EQ(getFrontend().moduleResolver.getModule("game/A"), nullptr);
1413
ParseOptions parseOptions;
1414
parseOptions.captureComments = true;
1415
SourceModule sourceMod;
1416
ParseResult parseResult = Parser::parse(source.c_str(), source.length(), *sourceMod.names, *sourceMod.allocator, parseOptions);
1417
FragmentContext context{source, parseResult, opts, std::nullopt};
1418
1419
FragmentAutocompleteStatusResult frag = Luau::tryFragmentAutocomplete(getFrontend(), "game/A", Position{2, 1}, context, nullCallback);
1420
REQUIRE(frag.result);
1421
REQUIRE(frag.result->incrementalModule);
1422
CHECK_EQ("game/A", frag.result->incrementalModule->name);
1423
CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr);
1424
CHECK_EQ(getFrontend().moduleResolver.getModule("game/A"), nullptr);
1425
}
1426
1427
TEST_SUITE_END();
1428
1429
1430
TEST_SUITE_BEGIN("FragmentAutocompleteTypeCheckerTests");
1431
1432
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_typecheck_simple_fragment")
1433
{
1434
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1435
auto res = checkWithOptions(
1436
R"(
1437
local x = 4
1438
local y = 5
1439
)"
1440
);
1441
1442
LUAU_REQUIRE_NO_ERRORS(res);
1443
1444
auto fragment = checkFragment(
1445
R"(
1446
local x = 4
1447
local y = 5
1448
local z = x + y
1449
)",
1450
Position{3, 15}
1451
);
1452
1453
auto opt = linearSearchForBinding(fragment.freshScope.get(), "z");
1454
REQUIRE(opt);
1455
CHECK_EQ("number", toString(*opt));
1456
}
1457
1458
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_typecheck_fragment_inserted_inline")
1459
{
1460
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1461
auto res = checkWithOptions(
1462
R"(
1463
local x = 4
1464
local y = 5
1465
)"
1466
);
1467
1468
LUAU_REQUIRE_NO_ERRORS(res);
1469
auto fragment = checkFragment(
1470
R"(
1471
local x = 4
1472
local z = x
1473
local y = 5
1474
)",
1475
Position{2, 11}
1476
);
1477
1478
auto correct = linearSearchForBinding(fragment.freshScope.get(), "z");
1479
REQUIRE(correct);
1480
CHECK_EQ("number", toString(*correct));
1481
}
1482
1483
TEST_SUITE_END();
1484
1485
1486
TEST_SUITE_BEGIN("MixedModeTests");
1487
1488
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_append")
1489
{
1490
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
1491
getFrontend().setLuauSolverMode(!FFlag::DebugLuauForceOldSolver ? SolverMode::New : SolverMode::Old);
1492
auto res = checkOldSolver(
1493
R"(
1494
local x = 4
1495
local y = 5
1496
)"
1497
);
1498
1499
LUAU_REQUIRE_NO_ERRORS(res);
1500
1501
auto fragment = checkFragment(
1502
R"(
1503
local x = 4
1504
local y = 5
1505
local z = x + y
1506
)",
1507
Position{3, 15}
1508
);
1509
1510
auto opt = linearSearchForBinding(fragment.freshScope.get(), "z");
1511
REQUIRE(opt);
1512
CHECK_EQ("number", toString(*opt));
1513
}
1514
1515
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined")
1516
{
1517
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
1518
getFrontend().setLuauSolverMode(!FFlag::DebugLuauForceOldSolver ? SolverMode::New : SolverMode::Old);
1519
auto res = checkOldSolver(
1520
R"(
1521
local x = 4
1522
local y = 5
1523
)"
1524
);
1525
1526
auto fragment = checkFragment(
1527
R"(
1528
local x = 4
1529
local z = x
1530
local y = 5
1531
)",
1532
Position{2, 11}
1533
);
1534
1535
auto correct = linearSearchForBinding(fragment.freshScope.get(), "z");
1536
REQUIRE(correct);
1537
CHECK_EQ("number", toString(*correct));
1538
}
1539
1540
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access")
1541
{
1542
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
1543
getFrontend().setLuauSolverMode(!FFlag::DebugLuauForceOldSolver ? SolverMode::New : SolverMode::Old);
1544
auto res = checkOldSolver(
1545
R"(
1546
local tbl = { abc = 1234}
1547
)"
1548
);
1549
1550
LUAU_REQUIRE_NO_ERRORS(res);
1551
1552
auto fragment = autocompleteFragment(
1553
R"(
1554
local tbl = { abc = 1234}
1555
tbl.
1556
)",
1557
Position{2, 5}
1558
);
1559
REQUIRE(fragment.result);
1560
LUAU_ASSERT(fragment.result->freshScope);
1561
1562
CHECK_EQ(1, fragment.result->acResults.entryMap.size());
1563
CHECK(fragment.result->acResults.entryMap.count("abc"));
1564
CHECK_EQ(AutocompleteContext::Property, fragment.result->acResults.context);
1565
}
1566
1567
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "typecheck_fragment_handles_unusable_module")
1568
{
1569
const std::string sourceA = "MainModule";
1570
fileResolver.source[sourceA] = R"(
1571
local Modules = game:GetService('Gui').Modules
1572
local B = require(Modules.B)
1573
return { hello = B }
1574
)";
1575
1576
const std::string sourceB = "game/Gui/Modules/B";
1577
fileResolver.source[sourceB] = R"(return {hello = "hello"})";
1578
1579
CheckResult result = getFrontend().check(sourceA, getOptions());
1580
CHECK(!getFrontend().isDirty(sourceA, getOptions().forAutocomplete));
1581
1582
std::weak_ptr<Module> weakModule = getModuleResolver(getFrontend()).getModule(sourceB);
1583
REQUIRE(!weakModule.expired());
1584
1585
getFrontend().markDirty(sourceB);
1586
CHECK(getFrontend().isDirty(sourceA, getOptions().forAutocomplete));
1587
1588
getFrontend().check(sourceB, getOptions());
1589
CHECK(weakModule.expired());
1590
1591
auto [status, _] = typecheckFragmentForModule(sourceA, fileResolver.source[sourceA], Luau::Position(0, 0));
1592
CHECK_EQ(status, FragmentTypeCheckStatus::SkipAutocomplete);
1593
1594
auto [status2, _2] = typecheckFragmentForModule(sourceB, fileResolver.source[sourceB], Luau::Position(3, 20));
1595
CHECK_EQ(status2, FragmentTypeCheckStatus::Success);
1596
}
1597
1598
TEST_SUITE_END();
1599
1600
TEST_SUITE_BEGIN("FragmentAutocompleteTests");
1601
1602
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete")
1603
{
1604
ToStringOptions opt;
1605
opt.exhaustive = true;
1606
opt.exhaustive = true;
1607
opt.functionTypeArguments = true;
1608
opt.maxTableLength = 0;
1609
opt.maxTypeLength = 0;
1610
1611
auto checkAndExamine = [&](const std::string& src, const std::string& idName, const std::string& idString)
1612
{
1613
checkWithOptions(src);
1614
auto id = getType(idName, true);
1615
LUAU_ASSERT(id);
1616
CHECK_EQ(Luau::toString(*id, opt), idString);
1617
};
1618
1619
auto getTypeFromModule = [](ModulePtr module, const std::string& name) -> std::optional<TypeId>
1620
{
1621
if (!module->hasModuleScope())
1622
return std::nullopt;
1623
return lookupName(module->getModuleScope(), name);
1624
};
1625
1626
auto fragmentACAndCheck = [&](const std::string& updated,
1627
const Position& pos,
1628
const std::string& idName,
1629
const std::string& srcIdString,
1630
const std::string& fragIdString)
1631
{
1632
FragmentAutocompleteStatusResult frag = autocompleteFragment(updated, pos, std::nullopt);
1633
REQUIRE(frag.result);
1634
auto fragId = getTypeFromModule(frag.result->incrementalModule, idName);
1635
LUAU_ASSERT(fragId);
1636
CHECK_EQ(Luau::toString(*fragId, opt), fragIdString);
1637
1638
auto srcId = getType(idName, true);
1639
LUAU_ASSERT(srcId);
1640
CHECK_EQ(Luau::toString(*srcId, opt), srcIdString);
1641
};
1642
1643
const std::string source = R"(local module = {}
1644
f
1645
return module)";
1646
1647
const std::string updated1 = R"(local module = {}
1648
function module.a
1649
return module)";
1650
1651
const std::string updated2 = R"(local module = {}
1652
function module.ab
1653
return module)";
1654
1655
{
1656
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
1657
getFrontend().setLuauSolverMode(SolverMode::Old);
1658
checkAndExamine(source, "module", "{| |}");
1659
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{| |}", "{| a: (%error-id%: unknown) -> () |}");
1660
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{| |}", "{| ab: (%error-id%: unknown) -> () |}");
1661
}
1662
{
1663
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1664
getFrontend().setLuauSolverMode(SolverMode::New);
1665
checkAndExamine(source, "module", "{ }");
1666
// [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on
1667
return;
1668
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
1669
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
1670
}
1671
}
1672
1673
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_autocomplete_simple_property_access")
1674
{
1675
1676
const std::string source = R"(
1677
local tbl = { abc = 1234}
1678
)";
1679
const std::string updated = R"(
1680
local tbl = { abc = 1234}
1681
tbl. @1
1682
)";
1683
1684
autocompleteFragmentInBothSolvers(
1685
source,
1686
updated,
1687
'1',
1688
[](FragmentAutocompleteStatusResult& fragment)
1689
{
1690
REQUIRE(fragment.result);
1691
auto acResults = fragment.result->acResults;
1692
1693
CHECK_EQ(1, acResults.entryMap.size());
1694
CHECK(acResults.entryMap.count("abc"));
1695
CHECK_EQ(AutocompleteContext::Property, acResults.context);
1696
}
1697
);
1698
}
1699
1700
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_autocomplete_nested_property_access")
1701
{
1702
const std::string source = R"(
1703
local tbl = { abc = { def = 1234, egh = false } }
1704
)";
1705
const std::string updated = R"(
1706
local tbl = { abc = { def = 1234, egh = false } }
1707
tbl.abc.@1
1708
)";
1709
autocompleteFragmentInBothSolvers(
1710
source,
1711
updated,
1712
'1',
1713
[](FragmentAutocompleteStatusResult& fragment)
1714
{
1715
REQUIRE(fragment.result);
1716
LUAU_ASSERT(fragment.result->freshScope);
1717
1718
CHECK_EQ(2, fragment.result->acResults.entryMap.size());
1719
CHECK(fragment.result->acResults.entryMap.count("def"));
1720
CHECK(fragment.result->acResults.entryMap.count("egh"));
1721
CHECK_EQ(fragment.result->acResults.context, AutocompleteContext::Property);
1722
}
1723
);
1724
}
1725
1726
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_functions_complex")
1727
{
1728
const std::string text = R"(@1 local function f1(a1)@2
1729
local l1 = 1;@3
1730
g1 = 1;@4
1731
end
1732
@5
1733
local function f2(a2)
1734
local l2 = 1;@6
1735
g2 = 1;
1736
end @7
1737
)";
1738
1739
autocompleteFragmentInBothSolvers(
1740
text,
1741
text,
1742
'1',
1743
[](FragmentAutocompleteStatusResult& fragment)
1744
{
1745
REQUIRE(fragment.result);
1746
auto strings = fragment.result->acResults.entryMap;
1747
CHECK(strings.count("f1") == 0);
1748
CHECK(strings.count("a1") == 0);
1749
CHECK(strings.count("l1") == 0);
1750
CHECK(strings.count("g1") != 0);
1751
CHECK(strings.count("f2") == 0);
1752
CHECK(strings.count("a2") == 0);
1753
CHECK(strings.count("l2") == 0);
1754
CHECK(strings.count("g2") != 0);
1755
}
1756
);
1757
1758
autocompleteFragmentInBothSolvers(
1759
text,
1760
text,
1761
'2',
1762
[](FragmentAutocompleteStatusResult& fragment)
1763
{
1764
REQUIRE(fragment.result);
1765
auto strings = fragment.result->acResults.entryMap;
1766
CHECK(strings.count("f1") != 0);
1767
CHECK(strings.count("a1") != 0);
1768
CHECK(strings.count("l1") == 0);
1769
CHECK(strings.count("g1") != 0);
1770
CHECK(strings.count("f2") == 0);
1771
CHECK(strings.count("a2") == 0);
1772
CHECK(strings.count("l2") == 0);
1773
CHECK(strings.count("g2") != 0);
1774
}
1775
);
1776
1777
autocompleteFragmentInBothSolvers(
1778
text,
1779
text,
1780
'3',
1781
[](FragmentAutocompleteStatusResult& fragment)
1782
{
1783
REQUIRE(fragment.result);
1784
auto strings = fragment.result->acResults.entryMap;
1785
CHECK(strings.count("f1") != 0);
1786
CHECK(strings.count("a1") != 0);
1787
CHECK(strings.count("l1") != 0);
1788
CHECK(strings.count("g1") != 0);
1789
CHECK(strings.count("f2") == 0);
1790
CHECK(strings.count("a2") == 0);
1791
CHECK(strings.count("l2") == 0);
1792
CHECK(strings.count("g2") != 0);
1793
}
1794
);
1795
1796
autocompleteFragmentInBothSolvers(
1797
text,
1798
text,
1799
'4',
1800
[](FragmentAutocompleteStatusResult& fragment)
1801
{
1802
REQUIRE(fragment.result);
1803
auto strings = fragment.result->acResults.entryMap;
1804
CHECK(strings.count("f1") != 0);
1805
CHECK(strings.count("a1") != 0);
1806
CHECK(strings.count("l1") != 0);
1807
CHECK(strings.count("g1") != 0);
1808
CHECK(strings.count("f2") == 0);
1809
CHECK(strings.count("a2") == 0);
1810
CHECK(strings.count("l2") == 0);
1811
CHECK(strings.count("g2") != 0);
1812
}
1813
);
1814
1815
autocompleteFragmentInBothSolvers(
1816
text,
1817
text,
1818
'5',
1819
[](FragmentAutocompleteStatusResult& fragment)
1820
{
1821
REQUIRE(fragment.result);
1822
auto strings = fragment.result->acResults.entryMap;
1823
CHECK(strings.count("f1") != 0);
1824
CHECK(strings.count("a1") == 0);
1825
CHECK(strings.count("l1") == 0);
1826
CHECK(strings.count("g1") != 0);
1827
CHECK(strings.count("f2") == 0);
1828
CHECK(strings.count("a2") == 0);
1829
CHECK(strings.count("l2") == 0);
1830
CHECK(strings.count("g2") != 0);
1831
}
1832
);
1833
1834
autocompleteFragmentInBothSolvers(
1835
text,
1836
text,
1837
'6',
1838
[](FragmentAutocompleteStatusResult& fragment)
1839
{
1840
REQUIRE(fragment.result);
1841
auto strings = fragment.result->acResults.entryMap;
1842
CHECK(strings.count("f1") != 0);
1843
CHECK(strings.count("a1") == 0);
1844
CHECK(strings.count("l1") == 0);
1845
CHECK(strings.count("g1") != 0);
1846
CHECK(strings.count("f2") != 0);
1847
CHECK(strings.count("a2") != 0);
1848
CHECK(strings.count("l2") != 0);
1849
CHECK(strings.count("g2") != 0);
1850
}
1851
);
1852
1853
autocompleteFragmentInBothSolvers(
1854
text,
1855
text,
1856
'7',
1857
[](FragmentAutocompleteStatusResult& fragment)
1858
{
1859
REQUIRE(fragment.result);
1860
auto strings = fragment.result->acResults.entryMap;
1861
CHECK(strings.count("f1") != 0);
1862
CHECK(strings.count("a1") == 0);
1863
CHECK(strings.count("l1") == 0);
1864
CHECK(strings.count("g1") != 0);
1865
CHECK(strings.count("f2") != 0);
1866
CHECK(strings.count("a2") == 0);
1867
CHECK(strings.count("l2") == 0);
1868
CHECK(strings.count("g2") != 0);
1869
}
1870
);
1871
}
1872
1873
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inline_autocomplete_picks_the_right_scope_1")
1874
{
1875
const std::string source = R"(
1876
type Table = { a: number, b: number }
1877
do
1878
type Table = { x: string, y: string }
1879
end
1880
)";
1881
1882
const std::string updated = R"(
1883
type Table = { a: number, b: number }
1884
do
1885
type Table = { x: string, y: string }
1886
local a : T@1
1887
end
1888
)";
1889
1890
autocompleteFragmentInBothSolvers(
1891
source,
1892
updated,
1893
'1',
1894
[](FragmentAutocompleteStatusResult& fragment)
1895
{
1896
REQUIRE(fragment.result);
1897
LUAU_ASSERT(fragment.result->freshScope);
1898
REQUIRE(fragment.result->acResults.entryMap.count("Table"));
1899
REQUIRE(fragment.result->acResults.entryMap["Table"].type);
1900
const TableType* tv = get<TableType>(follow(*fragment.result->acResults.entryMap["Table"].type));
1901
REQUIRE(tv);
1902
CHECK(tv->props.count("x"));
1903
}
1904
);
1905
}
1906
1907
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inline_autocomplete_picks_the_right_scope_2")
1908
{
1909
const std::string source = R"(
1910
type Table = { a: number, b: number }
1911
do
1912
type Table = { x: string, y: string }
1913
end
1914
)";
1915
1916
const std::string updated = R"(
1917
type Table = { a: number, b: number }
1918
do
1919
type Table = { x: string, y: string }
1920
end
1921
local a : T@1
1922
)";
1923
1924
autocompleteFragmentInBothSolvers(
1925
source,
1926
updated,
1927
'1',
1928
[](FragmentAutocompleteStatusResult& fragment)
1929
{
1930
REQUIRE(fragment.result);
1931
LUAU_ASSERT(fragment.result->freshScope);
1932
REQUIRE(fragment.result->acResults.entryMap.count("Table"));
1933
REQUIRE(fragment.result->acResults.entryMap["Table"].type);
1934
const TableType* tv = get<TableType>(follow(*fragment.result->acResults.entryMap["Table"].type));
1935
REQUIRE(tv);
1936
CHECK(tv->props.count("a"));
1937
CHECK(tv->props.count("b"));
1938
}
1939
);
1940
}
1941
1942
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "nested_recursive_function")
1943
{
1944
const std::string source = R"(
1945
function foo()
1946
@1end
1947
)";
1948
autocompleteFragmentInBothSolvers(
1949
source,
1950
source,
1951
'1',
1952
[](FragmentAutocompleteStatusResult& fragment)
1953
{
1954
REQUIRE(fragment.result);
1955
CHECK(fragment.result->acResults.entryMap.count("foo"));
1956
CHECK_EQ(AutocompleteContext::Statement, fragment.result->acResults.context);
1957
}
1958
);
1959
}
1960
1961
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "string_literal_with_override")
1962
{
1963
const std::string source = R"(
1964
function foo(bar: string) end
1965
foo("a@1bc")
1966
)";
1967
1968
autocompleteFragmentInBothSolvers(
1969
source,
1970
source,
1971
'1',
1972
[](FragmentAutocompleteStatusResult& fragment)
1973
{
1974
REQUIRE(fragment.result);
1975
CHECK(fragment.result->acResults.entryMap.empty());
1976
CHECK_EQ(AutocompleteContext::String, fragment.result->acResults.context);
1977
},
1978
Position{2, 9}
1979
);
1980
}
1981
1982
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "empty_program")
1983
{
1984
autocompleteFragmentInBothSolvers(
1985
"",
1986
"@1",
1987
'1',
1988
[](FragmentAutocompleteStatusResult& frag)
1989
{
1990
REQUIRE(frag.result);
1991
auto ac = frag.result->acResults;
1992
CHECK(ac.entryMap.count("table"));
1993
CHECK(ac.entryMap.count("math"));
1994
CHECK_EQ(ac.context, AutocompleteContext::Statement);
1995
}
1996
);
1997
}
1998
1999
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_initializer")
2000
{
2001
const std::string source = "local a =@1";
2002
autocompleteFragmentInBothSolvers(
2003
source,
2004
source,
2005
'1',
2006
[](FragmentAutocompleteStatusResult& frag)
2007
{
2008
REQUIRE(frag.result);
2009
auto ac = frag.result->acResults;
2010
2011
CHECK(ac.entryMap.count("table"));
2012
CHECK(ac.entryMap.count("math"));
2013
CHECK_EQ(ac.context, AutocompleteContext::Expression);
2014
}
2015
);
2016
}
2017
2018
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "leave_numbers_alone")
2019
{
2020
const std::string source = "local a = 3.@1";
2021
2022
autocompleteFragmentInBothSolvers(
2023
source,
2024
source,
2025
'1',
2026
[](FragmentAutocompleteStatusResult& frag)
2027
{
2028
REQUIRE(frag.result);
2029
auto ac = frag.result->acResults;
2030
CHECK(ac.entryMap.empty());
2031
CHECK_EQ(ac.context, AutocompleteContext::Unknown);
2032
}
2033
);
2034
}
2035
2036
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "user_defined_globals")
2037
{
2038
const std::string source = "local myLocal = 4;@1 ";
2039
2040
autocompleteFragmentInBothSolvers(
2041
source,
2042
source,
2043
'1',
2044
[](FragmentAutocompleteStatusResult& frag)
2045
{
2046
REQUIRE(frag.result);
2047
auto ac = frag.result->acResults;
2048
2049
CHECK(ac.entryMap.count("myLocal"));
2050
CHECK(ac.entryMap.count("table"));
2051
CHECK(ac.entryMap.count("math"));
2052
CHECK_EQ(ac.context, AutocompleteContext::Statement);
2053
}
2054
);
2055
}
2056
2057
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "dont_suggest_local_before_its_definition")
2058
{
2059
const std::string source = R"(
2060
local myLocal = 4
2061
function abc()
2062
@1 local myInnerLocal = 1
2063
@2
2064
end
2065
@3 )";
2066
2067
// autocomplete after abc but before myInnerLocal
2068
autocompleteFragmentInBothSolvers(
2069
source,
2070
source,
2071
'1',
2072
[](FragmentAutocompleteStatusResult& fragment)
2073
{
2074
REQUIRE(fragment.result);
2075
auto ac = fragment.result->acResults;
2076
CHECK(ac.entryMap.count("myLocal"));
2077
LUAU_CHECK_HAS_NO_KEY(ac.entryMap, "myInnerLocal");
2078
}
2079
);
2080
// autocomplete after my inner local
2081
autocompleteFragmentInBothSolvers(
2082
source,
2083
source,
2084
'2',
2085
[](FragmentAutocompleteStatusResult& fragment)
2086
{
2087
REQUIRE(fragment.result);
2088
auto ac = fragment.result->acResults;
2089
CHECK(ac.entryMap.count("myLocal"));
2090
CHECK(ac.entryMap.count("myInnerLocal"));
2091
}
2092
);
2093
2094
// autocomplete after abc, but don't include myInnerLocal(in the hidden scope)
2095
autocompleteFragmentInBothSolvers(
2096
source,
2097
source,
2098
'3',
2099
[](FragmentAutocompleteStatusResult& fragment)
2100
{
2101
REQUIRE(fragment.result);
2102
auto ac = fragment.result->acResults;
2103
CHECK(ac.entryMap.count("myLocal"));
2104
LUAU_CHECK_HAS_NO_KEY(ac.entryMap, "myInnerLocal");
2105
}
2106
);
2107
}
2108
2109
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "nested_recursive_function")
2110
{
2111
const std::string source = R"(
2112
local function outer()
2113
local function inner()
2114
@1 end
2115
end
2116
)";
2117
2118
autocompleteFragmentInBothSolvers(
2119
source,
2120
source,
2121
'1',
2122
[](FragmentAutocompleteStatusResult& frag)
2123
{
2124
REQUIRE(frag.result);
2125
auto ac = frag.result->acResults;
2126
CHECK(ac.entryMap.count("inner"));
2127
CHECK(ac.entryMap.count("outer"));
2128
}
2129
);
2130
}
2131
2132
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "user_defined_local_functions_in_own_definition")
2133
{
2134
const std::string source = R"(
2135
local function abc()
2136
@1
2137
end
2138
)";
2139
// Autocomplete inside of abc
2140
autocompleteFragmentInBothSolvers(
2141
source,
2142
source,
2143
'1',
2144
[](FragmentAutocompleteStatusResult& frag)
2145
{
2146
REQUIRE(frag.result);
2147
auto ac = frag.result->acResults;
2148
CHECK(ac.entryMap.count("abc"));
2149
CHECK(ac.entryMap.count("table"));
2150
CHECK(ac.entryMap.count("math"));
2151
}
2152
);
2153
}
2154
2155
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "global_functions_are_not_scoped_lexically")
2156
{
2157
const std::string source = R"(
2158
if true then
2159
function abc()
2160
2161
end
2162
end
2163
@1 )";
2164
autocompleteFragmentInBothSolvers(
2165
source,
2166
source,
2167
'1',
2168
[](FragmentAutocompleteStatusResult& frag)
2169
{
2170
REQUIRE(frag.result);
2171
auto ac = frag.result->acResults;
2172
CHECK(!ac.entryMap.empty());
2173
CHECK(ac.entryMap.count("abc"));
2174
CHECK(ac.entryMap.count("table"));
2175
CHECK(ac.entryMap.count("math"));
2176
}
2177
);
2178
}
2179
2180
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_functions_fall_out_of_scope")
2181
{
2182
const std::string source = R"(
2183
if true then
2184
local function abc()
2185
2186
end
2187
end
2188
@1 )";
2189
2190
autocompleteFragmentInBothSolvers(
2191
source,
2192
source,
2193
'1',
2194
[](FragmentAutocompleteStatusResult& frag)
2195
{
2196
REQUIRE(frag.result);
2197
auto ac = frag.result->acResults;
2198
CHECK_NE(0, ac.entryMap.size());
2199
LUAU_CHECK_HAS_NO_KEY(ac.entryMap, "abc");
2200
}
2201
);
2202
}
2203
2204
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "function_parameters")
2205
{
2206
const std::string source = R"(
2207
function abc(test)
2208
2209
@1 end
2210
)";
2211
2212
autocompleteFragmentInBothSolvers(
2213
source,
2214
source,
2215
'1',
2216
[](FragmentAutocompleteStatusResult& frag)
2217
{
2218
REQUIRE(frag.result);
2219
auto ac = frag.result->acResults;
2220
CHECK(ac.entryMap.count("test"));
2221
}
2222
);
2223
}
2224
2225
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "unsealed_table")
2226
{
2227
const std::string source = R"(
2228
local tbl = {}
2229
tbl.prop = 5
2230
tbl.@1
2231
)";
2232
2233
autocompleteFragmentInBothSolvers(
2234
source,
2235
source,
2236
'1',
2237
[](FragmentAutocompleteStatusResult& frag)
2238
{
2239
REQUIRE(frag.result);
2240
auto ac = frag.result->acResults;
2241
CHECK_EQ(1, ac.entryMap.size());
2242
CHECK(ac.entryMap.count("prop"));
2243
CHECK_EQ(ac.context, AutocompleteContext::Property);
2244
}
2245
);
2246
}
2247
2248
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "unsealed_table_2")
2249
{
2250
const std::string source = R"(
2251
local tbl = {}
2252
local inner = { prop = 5 }
2253
tbl.inner = inner
2254
tbl.inner.@1
2255
)";
2256
2257
autocompleteFragmentInBothSolvers(
2258
source,
2259
source,
2260
'1',
2261
[](FragmentAutocompleteStatusResult& frag)
2262
{
2263
REQUIRE(frag.result);
2264
auto ac = frag.result->acResults;
2265
CHECK_EQ(1, ac.entryMap.size());
2266
CHECK(ac.entryMap.count("prop"));
2267
CHECK_EQ(ac.context, AutocompleteContext::Property);
2268
}
2269
);
2270
}
2271
2272
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "cyclic_table")
2273
{
2274
const std::string source = R"(
2275
local abc = {}
2276
local def = { abc = abc }
2277
abc.def = def
2278
abc.def.@1
2279
)";
2280
2281
autocompleteFragmentInBothSolvers(
2282
source,
2283
source,
2284
'1',
2285
[](FragmentAutocompleteStatusResult& frag)
2286
{
2287
REQUIRE(frag.result);
2288
auto ac = frag.result->acResults;
2289
CHECK(ac.entryMap.count("abc"));
2290
CHECK_EQ(ac.context, AutocompleteContext::Property);
2291
}
2292
);
2293
}
2294
2295
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "table_union")
2296
{
2297
const std::string source = R"(
2298
type t1 = { a1 : string, b2 : number }
2299
type t2 = { b2 : string, c3 : string }
2300
function func(abc : t1 | t2)
2301
2302
end
2303
)";
2304
const std::string updated = R"(
2305
type t1 = { a1 : string, b2 : number }
2306
type t2 = { b2 : string, c3 : string }
2307
function func(abc : t1 | t2)
2308
abc.@1
2309
end
2310
)";
2311
2312
autocompleteFragmentInBothSolvers(
2313
source,
2314
updated,
2315
'1',
2316
[](FragmentAutocompleteStatusResult& frag)
2317
{
2318
REQUIRE(frag.result);
2319
auto ac = frag.result->acResults;
2320
CHECK_EQ(1, ac.entryMap.size());
2321
CHECK(ac.entryMap.count("b2"));
2322
CHECK_EQ(ac.context, AutocompleteContext::Property);
2323
}
2324
);
2325
}
2326
2327
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "table_intersection")
2328
{
2329
const std::string source = R"(
2330
type t1 = { a1 : string, b2 : number }
2331
type t2 = { b2 : number, c3 : string }
2332
function func(abc : t1 & t2)
2333
2334
end
2335
)";
2336
const std::string updated = R"(
2337
type t1 = { a1 : string, b2 : number }
2338
type t2 = { b2 : number, c3 : string }
2339
function func(abc : t1 & t2)
2340
abc.@1
2341
end
2342
)";
2343
2344
autocompleteFragmentInBothSolvers(
2345
source,
2346
updated,
2347
'1',
2348
[](FragmentAutocompleteStatusResult& frag)
2349
{
2350
REQUIRE(frag.result);
2351
auto ac = frag.result->acResults;
2352
CHECK_EQ(3, ac.entryMap.size());
2353
CHECK(ac.entryMap.count("a1"));
2354
CHECK(ac.entryMap.count("b2"));
2355
CHECK(ac.entryMap.count("c3"));
2356
CHECK_EQ(ac.context, AutocompleteContext::Property);
2357
}
2358
);
2359
}
2360
2361
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "get_suggestions_for_the_very_start_of_the_script")
2362
{
2363
const std::string source = R"(@1
2364
2365
function aaa() end
2366
)";
2367
2368
autocompleteFragmentInBothSolvers(
2369
source,
2370
source,
2371
'1',
2372
[](FragmentAutocompleteStatusResult& frag)
2373
{
2374
REQUIRE(frag.result);
2375
auto ac = frag.result->acResults;
2376
CHECK(ac.entryMap.count("table"));
2377
CHECK_EQ(ac.context, AutocompleteContext::Statement);
2378
}
2379
);
2380
}
2381
2382
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "studio_ice_1")
2383
{
2384
const std::string source = R"(
2385
--Woop
2386
\@native
2387
local function test()
2388
2389
end
2390
)";
2391
2392
const std::string updated = R"(
2393
--Woop
2394
\@native
2395
local function test()
2396
2397
end
2398
function a@1
2399
)";
2400
autocompleteFragmentInBothSolvers(source, updated, '1', [](FragmentAutocompleteStatusResult& result) {});
2401
}
2402
2403
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "method_call_inside_function_body")
2404
{
2405
const std::string source = R"(
2406
local game = { GetService=function(s) return 'hello' end }
2407
2408
function a()
2409
2410
end
2411
)";
2412
2413
const std::string updated = R"(
2414
local game = { GetService=function(s) return 'hello' end }
2415
2416
function a()
2417
game:@1
2418
end
2419
)";
2420
2421
autocompleteFragmentInBothSolvers(
2422
source,
2423
updated,
2424
'1',
2425
[](FragmentAutocompleteStatusResult& frag)
2426
{
2427
REQUIRE(frag.result);
2428
auto ac = frag.result->acResults;
2429
CHECK_NE(0, ac.entryMap.size());
2430
2431
LUAU_CHECK_HAS_NO_KEY(ac.entryMap, "math");
2432
CHECK_EQ(ac.context, AutocompleteContext::Property);
2433
}
2434
);
2435
}
2436
2437
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tbl_function_parameter")
2438
{
2439
const std::string source = R"(
2440
--!strict
2441
type Foo = {x : number, y : number}
2442
local function func(abc : Foo)
2443
abc.@1
2444
end
2445
)";
2446
2447
autocompleteFragmentInBothSolvers(
2448
source,
2449
source,
2450
'1',
2451
[](FragmentAutocompleteStatusResult& frag)
2452
{
2453
REQUIRE(frag.result);
2454
CHECK_EQ(2, frag.result->acResults.entryMap.size());
2455
CHECK(frag.result->acResults.entryMap.count("x"));
2456
CHECK(frag.result->acResults.entryMap.count("y"));
2457
}
2458
);
2459
}
2460
2461
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tbl_local_function_parameter")
2462
{
2463
const std::string source = R"(
2464
--!strict
2465
type Foo = {x : number, y : number}
2466
local function func(abc : Foo)
2467
abc.@1
2468
end
2469
)";
2470
2471
autocompleteFragmentInBothSolvers(
2472
source,
2473
source,
2474
'1',
2475
[](FragmentAutocompleteStatusResult& frag)
2476
{
2477
REQUIRE(frag.result);
2478
CHECK_EQ(2, frag.result->acResults.entryMap.size());
2479
CHECK(frag.result->acResults.entryMap.count("x"));
2480
CHECK(frag.result->acResults.entryMap.count("y"));
2481
}
2482
);
2483
}
2484
2485
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "vec3_function_parameter")
2486
{
2487
const std::string source = R"(
2488
--!strict
2489
local function func(abc : FakeVec)
2490
abc.@1
2491
end
2492
)";
2493
2494
autocompleteFragmentInBothSolvers(
2495
source,
2496
source,
2497
'1',
2498
[](FragmentAutocompleteStatusResult& frag)
2499
{
2500
REQUIRE(frag.result);
2501
CHECK_EQ(2, frag.result->acResults.entryMap.size());
2502
CHECK(frag.result->acResults.entryMap.count("zero"));
2503
CHECK(frag.result->acResults.entryMap.count("dot"));
2504
}
2505
);
2506
}
2507
2508
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "vec3_local_function_parameter")
2509
{
2510
const std::string source = R"(
2511
--!strict
2512
local function func(abc : FakeVec)
2513
abc.@1
2514
end
2515
)";
2516
2517
autocompleteFragmentInBothSolvers(
2518
source,
2519
source,
2520
'1',
2521
[](FragmentAutocompleteStatusResult& frag)
2522
{
2523
REQUIRE(frag.result);
2524
CHECK_EQ(2, frag.result->acResults.entryMap.size());
2525
CHECK(frag.result->acResults.entryMap.count("zero"));
2526
CHECK(frag.result->acResults.entryMap.count("dot"));
2527
}
2528
);
2529
}
2530
2531
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "function_parameter_not_recommending_out_of_scope_argument")
2532
{
2533
const std::string source = R"(
2534
--!strict
2535
local function foo(abd: FakeVec)
2536
end
2537
local function bar(abc : FakeVec)
2538
a@1
2539
end
2540
)";
2541
2542
autocompleteFragmentInBothSolvers(
2543
source,
2544
source,
2545
'1',
2546
[](FragmentAutocompleteStatusResult& frag)
2547
{
2548
REQUIRE(frag.result);
2549
CHECK(frag.result->acResults.entryMap.count("abc"));
2550
CHECK(!frag.result->acResults.entryMap.count("abd"));
2551
}
2552
);
2553
}
2554
2555
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "bad_range_1")
2556
{
2557
const std::string source = R"(
2558
local t = 1
2559
)";
2560
const std::string updated = R"(
2561
t
2562
@1)";
2563
2564
autocompleteFragmentInBothSolvers(
2565
source,
2566
updated,
2567
'1',
2568
[](FragmentAutocompleteStatusResult& frag)
2569
{
2570
REQUIRE(frag.result);
2571
auto opt = linearSearchForBinding(frag.result->freshScope, "t");
2572
REQUIRE(opt);
2573
CHECK_EQ("number", toString(*opt));
2574
}
2575
);
2576
}
2577
2578
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "bad_range_2")
2579
{
2580
const std::string source = R"(
2581
local t = 1
2582
)";
2583
const std::string updated = R"(
2584
local t = 1
2585
t@1
2586
)";
2587
2588
autocompleteFragmentInBothSolvers(
2589
source,
2590
updated,
2591
'1',
2592
[](FragmentAutocompleteStatusResult& frag)
2593
{
2594
REQUIRE(frag.result);
2595
auto opt = linearSearchForBinding(frag.result->freshScope, "t");
2596
REQUIRE(opt);
2597
CHECK_EQ("number", toString(*opt));
2598
}
2599
);
2600
}
2601
2602
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "bad_range_3")
2603
{
2604
// This test makes less sense since we don't have an updated check that
2605
// includes l
2606
// instead this will recommend nothing useful because `local t` hasn't
2607
// been typechecked in the fresh module
2608
const std::string source = R"(
2609
l
2610
)";
2611
const std::string updated = R"(
2612
local t = 1
2613
l@1
2614
)";
2615
2616
autocompleteFragmentInBothSolvers(
2617
source,
2618
updated,
2619
'1',
2620
[](FragmentAutocompleteStatusResult& frag)
2621
{
2622
CHECK(frag.status == FragmentAutocompleteStatus::Success);
2623
REQUIRE(frag.result);
2624
}
2625
);
2626
}
2627
2628
2629
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "do_not_recommend_results_in_multiline_comment")
2630
{
2631
std::string source = R"(--[[
2632
)";
2633
std::string dest = R"(--[[
2634
a@1
2635
)";
2636
2637
2638
autocompleteFragmentInBothSolvers(
2639
source,
2640
dest,
2641
'1',
2642
[](FragmentAutocompleteStatusResult& frag)
2643
{
2644
CHECK(frag.result == std::nullopt);
2645
}
2646
);
2647
}
2648
2649
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_simple")
2650
{
2651
const std::string source = R"(
2652
-- sel
2653
-- retur
2654
-- fo
2655
-- if @1
2656
-- end
2657
-- the
2658
)";
2659
autocompleteFragmentInBothSolvers(
2660
source,
2661
source,
2662
'1',
2663
[](FragmentAutocompleteStatusResult& frag)
2664
{
2665
CHECK(frag.result == std::nullopt);
2666
}
2667
);
2668
}
2669
2670
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_blocks")
2671
{
2672
const std::string source = R"(
2673
--[[
2674
comment 1
2675
@1]]@2 local
2676
-- [[ comment 2]]
2677
--
2678
-- sdfsdfsdf
2679
--[[comment 3]]
2680
--[[ @3
2681
foo
2682
@4bar
2683
baz
2684
]]
2685
)";
2686
autocompleteFragmentInBothSolvers(
2687
source,
2688
source,
2689
'1',
2690
[](FragmentAutocompleteStatusResult& frag)
2691
{
2692
CHECK(frag.result == std::nullopt);
2693
}
2694
);
2695
2696
autocompleteFragmentInBothSolvers(
2697
source,
2698
source,
2699
'2',
2700
[](FragmentAutocompleteStatusResult& frag)
2701
{
2702
REQUIRE(frag.result);
2703
CHECK(!frag.result->acResults.entryMap.empty());
2704
}
2705
);
2706
2707
autocompleteFragmentInBothSolvers(
2708
source,
2709
source,
2710
'3',
2711
[](FragmentAutocompleteStatusResult& frag)
2712
{
2713
CHECK(frag.result == std::nullopt);
2714
}
2715
);
2716
2717
autocompleteFragmentInBothSolvers(
2718
source,
2719
source,
2720
'4',
2721
[](FragmentAutocompleteStatusResult& frag)
2722
{
2723
CHECK(frag.result == std::nullopt);
2724
}
2725
);
2726
}
2727
2728
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments")
2729
{
2730
const std::string source = R"(
2731
-- sel @1
2732
-- retur @2
2733
-- fo @3
2734
--[[ sel @4]]
2735
local @5 -- hell@6o
2736
)";
2737
autocompleteFragmentInBothSolvers(
2738
source,
2739
source,
2740
'1',
2741
[](FragmentAutocompleteStatusResult& frag)
2742
{
2743
CHECK(frag.result == std::nullopt);
2744
}
2745
);
2746
2747
autocompleteFragmentInBothSolvers(
2748
source,
2749
source,
2750
'2',
2751
[](FragmentAutocompleteStatusResult& frag)
2752
{
2753
CHECK(frag.result == std::nullopt);
2754
}
2755
);
2756
2757
autocompleteFragmentInBothSolvers(
2758
source,
2759
source,
2760
'3',
2761
[](FragmentAutocompleteStatusResult& frag)
2762
{
2763
CHECK(frag.result == std::nullopt);
2764
}
2765
);
2766
2767
autocompleteFragmentInBothSolvers(
2768
source,
2769
source,
2770
'4',
2771
[](FragmentAutocompleteStatusResult& frag)
2772
{
2773
CHECK(frag.result == std::nullopt);
2774
}
2775
);
2776
2777
autocompleteFragmentInBothSolvers(
2778
source,
2779
source,
2780
'5',
2781
[](FragmentAutocompleteStatusResult& frag)
2782
{
2783
REQUIRE(frag.result);
2784
CHECK(!frag.result->acResults.entryMap.empty());
2785
}
2786
);
2787
2788
autocompleteFragmentInBothSolvers(
2789
source,
2790
source,
2791
'6',
2792
[](FragmentAutocompleteStatusResult& frag)
2793
{
2794
CHECK(frag.result == std::nullopt);
2795
}
2796
);
2797
}
2798
2799
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_in_incremental_fragment")
2800
{
2801
const std::string source = R"(
2802
local x = 5
2803
if x == 5
2804
)";
2805
const std::string updated = R"(
2806
local x = 5
2807
if x == 5 then -- a comment @1
2808
)";
2809
autocompleteFragmentInBothSolvers(
2810
source,
2811
updated,
2812
'1',
2813
[](FragmentAutocompleteStatusResult& frag)
2814
{
2815
CHECK(frag.result == std::nullopt);
2816
}
2817
);
2818
}
2819
2820
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_handles_parse_errors")
2821
{
2822
2823
ScopedFastInt sfi{FInt::LuauParseErrorLimit, 1};
2824
const std::string source = R"(
2825
2826
)";
2827
const std::string updated = R"(
2828
type A = <>random non code text here @1
2829
)";
2830
2831
autocompleteFragmentInBothSolvers(
2832
source,
2833
updated,
2834
'1',
2835
[](FragmentAutocompleteStatusResult& frag)
2836
{
2837
REQUIRE(frag.result);
2838
CHECK(frag.result->acResults.entryMap.empty());
2839
}
2840
);
2841
}
2842
2843
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "require_tracing")
2844
{
2845
fileResolver.source["MainModule/A"] = R"(
2846
return { x = 0 }
2847
)";
2848
2849
fileResolver.source["MainModule"] = R"(
2850
local result = require(script.A)
2851
local x = 1 + result.@1
2852
)";
2853
2854
autocompleteFragmentInBothSolvers(
2855
fileResolver.source["MainModule"],
2856
fileResolver.source["MainModule"],
2857
'1',
2858
[](FragmentAutocompleteStatusResult& frag)
2859
{
2860
REQUIRE(frag.result);
2861
CHECK(frag.result->acResults.entryMap.size() == 1);
2862
CHECK(frag.result->acResults.entryMap.count("x"));
2863
}
2864
);
2865
}
2866
2867
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "fragment_ac_must_traverse_typeof_and_not_ice")
2868
{
2869
// This test ensures that we traverse typeof expressions for defs that are being referred to in the fragment
2870
// In this case, we want to ensure we populate the incremental environment with the reference to `m`
2871
// Without this, we would ice as we will refer to the local `m` before it's declaration
2872
const std::string source = R"(
2873
--!strict
2874
local m = {}
2875
-- and here
2876
function m:m1() end
2877
type nt = typeof(m)
2878
2879
return m
2880
)";
2881
const std::string updated = R"(
2882
--!strict
2883
local m = {}
2884
-- and here
2885
function m:m1() end
2886
type nt = typeof(m)
2887
l @1
2888
return m
2889
)";
2890
2891
autocompleteFragmentInBothSolvers(source, updated, '1', [](FragmentAutocompleteStatusResult& _) {});
2892
}
2893
2894
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "duped_alias")
2895
{
2896
const std::string source = R"(
2897
type a = typeof({})
2898
2899
)";
2900
const std::string dest = R"(
2901
type a = typeof({})
2902
type a = typeof({})@1
2903
)";
2904
2905
// Re-parsing and typechecking a type alias in the fragment that was defined in the base module will assert in ConstraintGenerator::checkAliases
2906
// unless we don't clone it This will let the incremental pass re-generate the type binding, and we will expect to see it in the type bindings
2907
autocompleteFragmentInBothSolvers(
2908
source,
2909
dest,
2910
'1',
2911
[](FragmentAutocompleteStatusResult& frag)
2912
{
2913
REQUIRE(frag.result);
2914
Scope* sc = frag.result->freshScope;
2915
CHECK(1 == sc->privateTypeBindings.count("a"));
2916
}
2917
);
2918
}
2919
2920
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "mutually_recursive_alias")
2921
{
2922
const std::string source = R"(
2923
type U = {f : number, g : U}
2924
2925
)";
2926
const std::string dest = R"(
2927
type U = {f : number, g : V}
2928
type V = {h : number, i : U?} @1
2929
)";
2930
2931
// Re-parsing and typechecking a type alias in the fragment that was defined in the base module will assert in ConstraintGenerator::checkAliases
2932
// unless we don't clone it This will let the incremental pass re-generate the type binding, and we will expect to see it in the type bindings
2933
autocompleteFragmentInBothSolvers(
2934
source,
2935
dest,
2936
'1',
2937
[](FragmentAutocompleteStatusResult& frag)
2938
{
2939
REQUIRE(frag.result->freshScope);
2940
Scope* scope = frag.result->freshScope;
2941
CHECK(1 == scope->privateTypeBindings.count("U"));
2942
CHECK(1 == scope->privateTypeBindings.count("V"));
2943
}
2944
);
2945
}
2946
2947
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "generalization_crash_when_old_solver_freetypes_have_no_bounds_set")
2948
{
2949
const std::string source = R"(
2950
local UserInputService = game:GetService("UserInputService");
2951
2952
local Camera = workspace.CurrentCamera;
2953
2954
UserInputService.InputBegan:Connect(function(Input)
2955
if (Input.KeyCode == Enum.KeyCode.One) then
2956
local Up = Input.Foo
2957
local Vector = -(Up:Unit)
2958
end
2959
end)
2960
)";
2961
2962
const std::string dest = R"(
2963
local UserInputService = game:GetService("UserInputService");
2964
2965
local Camera = workspace.CurrentCamera;
2966
2967
UserInputService.InputBegan:Connect(function(Input)
2968
if (Input.KeyCode == Enum.KeyCode.One) then
2969
local Up = Input.Foo
2970
local Vector = -(Up:Unit()) @1
2971
end
2972
end)
2973
)";
2974
2975
autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {});
2976
}
2977
2978
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation")
2979
{
2980
ToStringOptions opt;
2981
opt.exhaustive = true;
2982
opt.exhaustive = true;
2983
opt.functionTypeArguments = true;
2984
opt.maxTableLength = 0;
2985
opt.maxTypeLength = 0;
2986
2987
auto checkAndExamine = [&](const std::string& src, const std::string& idName, const std::string& idString)
2988
{
2989
checkWithOptions(src);
2990
auto id = getType(idName, true);
2991
LUAU_ASSERT(id);
2992
CHECK_EQ(Luau::toString(*id, opt), idString);
2993
};
2994
2995
auto getTypeFromModule = [](ModulePtr module, const std::string& name) -> std::optional<TypeId>
2996
{
2997
if (!module->hasModuleScope())
2998
return std::nullopt;
2999
return lookupName(module->getModuleScope(), name);
3000
};
3001
3002
auto fragmentACAndCheck = [&](const std::string& updated, const Position& pos, const std::string& idName)
3003
{
3004
FragmentAutocompleteStatusResult frag = autocompleteFragment(updated, pos, std::nullopt);
3005
REQUIRE(frag.result);
3006
auto fragId = getTypeFromModule(frag.result->incrementalModule, idName);
3007
LUAU_ASSERT(fragId);
3008
3009
auto srcId = getType(idName, true);
3010
LUAU_ASSERT(srcId);
3011
3012
CHECK((*fragId)->owningArena != (*srcId)->owningArena);
3013
CHECK(&(frag.result->incrementalModule->internalTypes) == (*fragId)->owningArena);
3014
};
3015
3016
const std::string source = R"(local module = {}
3017
f
3018
return module)";
3019
3020
const std::string updated1 = R"(local module = {}
3021
function module.a
3022
return module)";
3023
3024
const std::string updated2 = R"(local module = {}
3025
function module.ab
3026
return module)";
3027
3028
{
3029
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, true};
3030
getFrontend().setLuauSolverMode(SolverMode::Old);
3031
checkAndExamine(source, "module", "{| |}");
3032
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
3033
// early return since the following checking will fail, which it shouldn't!
3034
fragmentACAndCheck(updated1, Position{1, 17}, "module");
3035
fragmentACAndCheck(updated2, Position{1, 18}, "module");
3036
}
3037
3038
{
3039
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
3040
getFrontend().setLuauSolverMode(SolverMode::New);
3041
checkAndExamine(source, "module", "{ }");
3042
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
3043
// early return since the following checking will fail, which it shouldn't!
3044
fragmentACAndCheck(updated1, Position{1, 17}, "module");
3045
fragmentACAndCheck(updated2, Position{1, 18}, "module");
3046
}
3047
}
3048
3049
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_shouldnt_crash_on_cross_module_mutation")
3050
{
3051
const std::string source = R"(local module = {}
3052
function module.
3053
return module
3054
)";
3055
3056
const std::string updated = R"(local module = {}
3057
function module.f@1
3058
return module
3059
)";
3060
3061
autocompleteFragmentInBothSolvers(source, updated, '1', [](FragmentAutocompleteStatusResult& result) {});
3062
}
3063
3064
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "ice_caused_by_mixed_mode_use")
3065
{
3066
const std::string source =
3067
std::string("--[[\n\tPackage link auto-generated by Rotriever\n]]\nlocal PackageIndex = script.Parent._Index\n\nlocal Package = ") +
3068
"require(PackageIndex[\"ReactOtter\"][\"ReactOtter\"])\n\nexport type Goal = Package.Goal\nexport type SpringOptions " +
3069
"= Package.SpringOptions\n\n\nreturn Pa@1";
3070
autocompleteFragmentInBothSolvers(
3071
source,
3072
source,
3073
'1',
3074
[](FragmentAutocompleteStatusResult& _) {
3075
3076
}
3077
);
3078
autocompleteFragmentInBothSolvers(source, source, '1', [](auto& _) {});
3079
}
3080
3081
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "free_type_in_old_solver_shouldnt_trigger_not_null_assertion")
3082
{
3083
3084
const std::string source = R"(--!strict
3085
local foo
3086
local a, z = foo()
3087
3088
local e = foo().x
3089
3090
local f = foo().y
3091
3092
z
3093
)";
3094
3095
const std::string dest = R"(--!strict
3096
local foo
3097
local a, z = foo()
3098
3099
local e = foo().x
3100
3101
local f = foo().y
3102
3103
z:a@1
3104
)";
3105
3106
autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {});
3107
}
3108
3109
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "interior_free_types_assertion_caused_by_free_type_inheriting_null_scope_from_table")
3110
{
3111
const std::string source = R"(--!strict
3112
local foo
3113
local a = foo()
3114
3115
local e = foo().x
3116
3117
local f = foo().y
3118
3119
3120
)";
3121
3122
const std::string dest = R"(--!strict
3123
local foo
3124
local a = foo()
3125
3126
local e = foo().x
3127
3128
local f = foo().y
3129
3130
z = a.P.E@1
3131
)";
3132
3133
autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {});
3134
}
3135
3136
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "NotNull_nil_scope_assertion_caused_by_free_type_inheriting_null_scope_from_table")
3137
{
3138
const std::string source = R"(--!strict
3139
local foo
3140
local a = foo()
3141
3142
local e = foo().x
3143
3144
local f = foo().y
3145
3146
3147
)";
3148
3149
const std::string dest = R"(--!strict
3150
local foo
3151
local a = foo()
3152
3153
local e = foo().x
3154
3155
local f = foo().y
3156
3157
z = a.P.E@1
3158
)";
3159
3160
autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {});
3161
}
3162
3163
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local")
3164
{
3165
const std::string source = R"(--!strict
3166
type function foo(x: type): type
3167
if x.tag == "singleton" then
3168
local t = x:value()
3169
3170
return types.unionof(types.singleton(t), types.singleton(nil))
3171
end
3172
3173
return types.number
3174
end
3175
)";
3176
3177
const std::string dest = R"(--!strict
3178
type function foo(x: type): type
3179
if x.tag == "singleton" then
3180
local t = x:value()
3181
x
3182
return types.unionof(types.singleton(t), types.singleton(nil))
3183
end
3184
3185
return types.number
3186
end
3187
)";
3188
3189
// Only checking in new solver as old solver doesn't handle type functions and constraint solver will ICE
3190
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
3191
this->check(source, getOptions());
3192
3193
FragmentAutocompleteStatusResult result = autocompleteFragment(dest, Position{4, 9}, std::nullopt);
3194
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
3195
}
3196
3197
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_loop_recommends")
3198
{
3199
const std::string source = R"(
3200
local testArr: {{a: number, b: number}} = {
3201
{a = 1, b = 2},
3202
{a = 2, b = 4},
3203
}
3204
3205
for _, v in testArr do
3206
3207
end
3208
)";
3209
3210
const std::string dest = R"(
3211
local testArr: {{a: number, b: number}} = {
3212
{a = 1, b = 2},
3213
{a = 2, b = 4},
3214
}
3215
3216
for _, v in testArr do
3217
print(v.@1
3218
end
3219
)";
3220
3221
autocompleteFragmentInBothSolvers(
3222
source,
3223
dest,
3224
'1',
3225
[](FragmentAutocompleteStatusResult& result)
3226
{
3227
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
3228
CHECK(result.result);
3229
CHECK(!result.result->acResults.entryMap.empty());
3230
CHECK(result.result->acResults.entryMap.count("a"));
3231
CHECK(result.result->acResults.entryMap.count("b"));
3232
}
3233
);
3234
}
3235
3236
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_loop_recommends")
3237
{
3238
const std::string source = R"(
3239
local testArr: {string} = {
3240
"a",
3241
"b",
3242
}
3243
3244
for _, v in testArr do
3245
3246
end
3247
)";
3248
3249
const std::string dest = R"(
3250
local testArr: {string} = {
3251
"a",
3252
"b",
3253
}
3254
3255
for _, v in testArr do
3256
print(v:@1)
3257
end
3258
)";
3259
3260
autocompleteFragmentInBothSolvers(
3261
source,
3262
dest,
3263
'1',
3264
[](FragmentAutocompleteStatusResult& result)
3265
{
3266
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
3267
CHECK(result.result);
3268
CHECK(!result.result->acResults.entryMap.empty());
3269
CHECK(result.result->acResults.entryMap.count("upper"));
3270
CHECK(result.result->acResults.entryMap.count("sub"));
3271
}
3272
);
3273
}
3274
3275
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "expr_function")
3276
{
3277
const std::string source = R"(
3278
local t = {}
3279
type Input = {x : string}
3280
function t.Do(fn : (Input) -> ())
3281
if t.x == "a" then
3282
return
3283
end
3284
end
3285
3286
t.Do(function (f)
3287
f
3288
end)
3289
)";
3290
3291
const std::string dest = R"(
3292
local t = {}
3293
type Input = {x : string}
3294
function t.Do(fn : (Input) -> ())
3295
if t.x == "a" then
3296
return
3297
end
3298
end
3299
3300
t.Do(function (f)
3301
f.@1
3302
end)
3303
)";
3304
3305
autocompleteFragmentInBothSolvers(
3306
source,
3307
dest,
3308
'1',
3309
[](FragmentAutocompleteStatusResult& status)
3310
{
3311
CHECK(FragmentAutocompleteStatus::Success == status.status);
3312
REQUIRE(status.result);
3313
CHECK(!status.result->acResults.entryMap.empty());
3314
CHECK(status.result->acResults.entryMap.count("x"));
3315
}
3316
);
3317
}
3318
3319
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "differ_1")
3320
{
3321
const std::string source = R"()";
3322
const std::string dest = R"(local tbl = { foo = 1, bar = 2 };
3323
tbl.b@1)";
3324
3325
autocompleteFragmentInBothSolvers(
3326
source,
3327
dest,
3328
'1',
3329
[](FragmentAutocompleteStatusResult& status)
3330
{
3331
CHECK(FragmentAutocompleteStatus::Success == status.status);
3332
REQUIRE(status.result);
3333
CHECK(!status.result->acResults.entryMap.empty());
3334
CHECK(status.result->acResults.entryMap.count("foo"));
3335
CHECK(status.result->acResults.entryMap.count("bar"));
3336
}
3337
);
3338
}
3339
3340
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_test_both_empty")
3341
{
3342
SourceModule stale;
3343
SourceModule fresh;
3344
ParseResult o = parseHelper_(stale, R"()");
3345
ParseResult n = parseHelper_(stale, R"()");
3346
auto pos = Luau::blockDiffStart(o.root, n.root, nullptr);
3347
CHECK(pos == std::nullopt);
3348
}
3349
3350
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_test_both_empty_e2e")
3351
{
3352
const std::string source = R"(@1)";
3353
3354
autocompleteFragmentInBothSolvers(
3355
source,
3356
source,
3357
'1',
3358
[](FragmentAutocompleteStatusResult& result)
3359
{
3360
CHECK(FragmentAutocompleteStatus::Success == result.status);
3361
}
3362
);
3363
}
3364
3365
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_1")
3366
{
3367
SourceModule stale;
3368
SourceModule fresh;
3369
ParseResult o = parseHelper_(stale, R"()");
3370
ParseResult n = parseHelper_(fresh, R"(local x = 4
3371
local y = 3
3372
local z = 3)");
3373
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[2]);
3374
REQUIRE(pos);
3375
CHECK(*pos == Position{0, 0});
3376
}
3377
3378
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_1_e2e")
3379
{
3380
const std::string source = R"()";
3381
const std::string dest = R"(local f1 = 4
3382
local f2 = "a"
3383
local f3 = f@1
3384
)";
3385
3386
autocompleteFragmentInBothSolvers(
3387
source,
3388
dest,
3389
'1',
3390
[](FragmentAutocompleteStatusResult& result)
3391
{
3392
CHECK(FragmentAutocompleteStatus::Success == result.status);
3393
REQUIRE(result.result);
3394
CHECK(!result.result->acResults.entryMap.empty());
3395
CHECK(result.result->acResults.entryMap.count("f1"));
3396
CHECK(result.result->acResults.entryMap.count("f2"));
3397
}
3398
);
3399
}
3400
3401
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_1_e2e_in_the_middle")
3402
{
3403
const std::string source = R"()";
3404
const std::string dest = R"(local f1 = 4
3405
local f2 = f@1
3406
local f3 = f
3407
)";
3408
3409
autocompleteFragmentInBothSolvers(
3410
source,
3411
dest,
3412
'1',
3413
[](FragmentAutocompleteStatusResult& result)
3414
{
3415
CHECK(FragmentAutocompleteStatus::Success == result.status);
3416
REQUIRE(result.result);
3417
CHECK(!result.result->acResults.entryMap.empty());
3418
CHECK(result.result->acResults.entryMap.count("f1"));
3419
CHECK(!result.result->acResults.entryMap.count("f3"));
3420
}
3421
);
3422
}
3423
3424
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_2")
3425
{
3426
SourceModule stale;
3427
SourceModule fresh;
3428
ParseResult o = parseHelper_(stale, R"(local x = 4)");
3429
ParseResult n = parseHelper_(fresh, R"(local x = 4
3430
local y = 3
3431
local z = 3)");
3432
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[1]);
3433
REQUIRE(pos);
3434
CHECK(*pos == Position{1, 0});
3435
}
3436
3437
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_3")
3438
{
3439
SourceModule stale;
3440
SourceModule fresh;
3441
ParseResult o = parseHelper_(stale, R"(local x = 4
3442
local y = 2 + 1)");
3443
ParseResult n = parseHelper_(fresh, R"(local x = 4
3444
local y = 3
3445
local z = 3
3446
local foo = 8)");
3447
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[3]);
3448
REQUIRE(pos);
3449
CHECK(*pos == Position{1, 0});
3450
}
3451
3452
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_3")
3453
{
3454
const std::string source = R"(local f1 = 4
3455
local f2 = 2 + 1)";
3456
const std::string dest = R"(local f1 = 4
3457
local f2 = 3
3458
local f3 = 3
3459
local foo = 8 + @1)";
3460
autocompleteFragmentInBothSolvers(
3461
source,
3462
dest,
3463
'1',
3464
[](FragmentAutocompleteStatusResult& result)
3465
{
3466
CHECK(FragmentAutocompleteStatus::Success == result.status);
3467
REQUIRE(result.result);
3468
CHECK(!result.result->acResults.entryMap.empty());
3469
CHECK(result.result->acResults.entryMap.count("f1"));
3470
CHECK(result.result->acResults.entryMap.count("f2"));
3471
CHECK(result.result->acResults.entryMap.count("f3"));
3472
}
3473
);
3474
}
3475
3476
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_fake_similarity")
3477
{
3478
// Captures the bad behaviour of block based diffs
3479
SourceModule stale;
3480
SourceModule fresh;
3481
ParseResult o = parseHelper_(stale, R"(local x = 4
3482
local y = true
3483
local z = 2 + 1)");
3484
ParseResult n = parseHelper_(fresh, R"(local x = 4
3485
local y = "tr"
3486
local z = 3
3487
local foo = 8)");
3488
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[2]);
3489
REQUIRE(pos);
3490
CHECK(*pos == Position{2, 0});
3491
}
3492
3493
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalReturn_assert")
3494
{
3495
const std::string source = R"()";
3496
const std::string dest = R"(local function target(a: number, b: string) return a + #b end
3497
local function bar1(a: string) reutrn a .. 'x' end
3498
local function bar2(a: number) return -a end
3499
return target(bar@1)";
3500
3501
autocompleteFragmentInBothSolvers(
3502
source,
3503
dest,
3504
'1',
3505
[](FragmentAutocompleteStatusResult& status)
3506
{
3507
CHECK(FragmentAutocompleteStatus::Success == status.status);
3508
REQUIRE(status.result);
3509
CHECK(!status.result->acResults.entryMap.empty());
3510
CHECK(status.result->acResults.entryMap.count("bar1"));
3511
CHECK(status.result->acResults.entryMap.count("bar2"));
3512
}
3513
);
3514
}
3515
3516
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalRank_assert")
3517
{
3518
const std::string source = R"()";
3519
const std::string dest = R"(local function target(a: number, b: string) return a + #b end
3520
local bar1 = 'hello'
3521
local bar2 = 4
3522
return target(bar@1)";
3523
3524
autocompleteFragmentInBothSolvers(
3525
source,
3526
dest,
3527
'1',
3528
[](FragmentAutocompleteStatusResult& status)
3529
{
3530
CHECK(FragmentAutocompleteStatus::Success == status.status);
3531
REQUIRE(status.result);
3532
CHECK(!status.result->acResults.entryMap.empty());
3533
CHECK(status.result->acResults.entryMap.count("bar1"));
3534
CHECK(status.result->acResults.entryMap.count("bar2"));
3535
}
3536
);
3537
}
3538
3539
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_finished_defining")
3540
{
3541
const std::string source = R"(local function foobar(): string return "" end
3542
local foo = f)";
3543
const std::string dest = R"(local function foobar(): string return "" end
3544
local foo = foobar()
3545
foo:@1)";
3546
3547
autocompleteFragmentInBothSolvers(
3548
source,
3549
dest,
3550
'1',
3551
[](FragmentAutocompleteStatusResult& res)
3552
{
3553
CHECK(FragmentAutocompleteStatus::Success == res.status);
3554
REQUIRE(res.result);
3555
CHECK(!res.result->acResults.entryMap.empty());
3556
CHECK(res.result->acResults.entryMap.count("len"));
3557
CHECK(res.result->acResults.entryMap.count("gsub"));
3558
}
3559
);
3560
}
3561
3562
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_redef")
3563
{
3564
const std::string source = R"(local x = 42)";
3565
const std::string dest = R"(local x = 42
3566
local x = ""
3567
x:@1)";
3568
3569
autocompleteFragmentInBothSolvers(
3570
source,
3571
dest,
3572
'1',
3573
[](FragmentAutocompleteStatusResult& res)
3574
{
3575
CHECK(FragmentAutocompleteStatus::Success == res.status);
3576
REQUIRE(res.result);
3577
CHECK(!res.result->acResults.entryMap.empty());
3578
CHECK(res.result->acResults.entryMap.count("len"));
3579
CHECK(res.result->acResults.entryMap.count("gsub"));
3580
}
3581
);
3582
}
3583
3584
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "diff_multiple_blocks_on_same_line")
3585
{
3586
const std::string source = R"(
3587
do local function foo() end; local x = ""; end do local function bar() end)";
3588
const std::string dest = R"(
3589
do local function foo() end; local x = ""; end do local function bar() end local x = {a : number}; b @1end )";
3590
3591
autocompleteFragmentInBothSolvers(
3592
source,
3593
dest,
3594
'1',
3595
[](FragmentAutocompleteStatusResult& res)
3596
{
3597
CHECK(FragmentAutocompleteStatus::Success == res.status);
3598
REQUIRE(res.result);
3599
CHECK(!res.result->acResults.entryMap.empty());
3600
CHECK(res.result->acResults.entryMap.count("bar"));
3601
CHECK(!res.result->acResults.entryMap.count("foo"));
3602
}
3603
);
3604
}
3605
3606
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "nested_blocks_else_simple")
3607
{
3608
const std::string source = R"(
3609
local function foo(t : {foo : string})
3610
local x = t.foo
3611
do
3612
if t then
3613
end
3614
end
3615
end
3616
)";
3617
const std::string dest = R"(
3618
local function foo(t : {foo : string})
3619
local x = t.foo
3620
do
3621
if t then
3622
x:@1
3623
end
3624
end
3625
end
3626
)";
3627
autocompleteFragmentInBothSolvers(
3628
source,
3629
dest,
3630
'1',
3631
[](FragmentAutocompleteStatusResult& res)
3632
{
3633
CHECK(FragmentAutocompleteStatus::Success == res.status);
3634
REQUIRE(res.result);
3635
CHECK(!res.result->acResults.entryMap.empty());
3636
CHECK(res.result->acResults.entryMap.count("gsub"));
3637
CHECK(res.result->acResults.entryMap.count("len"));
3638
}
3639
);
3640
}
3641
3642
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "nested_blocks_else_difficult_2")
3643
{
3644
const std::string source = R"(
3645
local function foo(t : {foo : number})
3646
do
3647
if t then
3648
end
3649
end
3650
end
3651
)";
3652
const std::string dest = R"(
3653
local function foo(t : {foo : number})
3654
do
3655
if t then
3656
else
3657
local x = 4
3658
return x + t@1.
3659
end
3660
end
3661
end
3662
)";
3663
autocompleteFragmentInBothSolvers(
3664
source,
3665
dest,
3666
'1',
3667
[](FragmentAutocompleteStatusResult& res)
3668
{
3669
CHECK(FragmentAutocompleteStatus::Success == res.status);
3670
REQUIRE(res.result);
3671
CHECK(!res.result->acResults.entryMap.empty());
3672
CHECK(res.result->acResults.entryMap.count("foo"));
3673
}
3674
);
3675
}
3676
3677
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "NotNull_assertion_caused_by_leaking_free_type_from_stale_module")
3678
{
3679
const std::string source = R"(
3680
local Players = game:GetService("Players")
3681
3682
Players.PlayerAdded:Connect(function(Player)
3683
for_,v in script.PlayerValue:GetChildren()do
3684
v
3685
end
3686
end)
3687
)";
3688
3689
const std::string dest = R"(
3690
local Players = game:GetService("Players")
3691
3692
Players.PlayerAdded:Connect(function(Player)
3693
for_,v in script.PlayerValue:GetChildren()do
3694
v:L@1
3695
end
3696
end)
3697
)";
3698
autocompleteFragmentInBothSolvers(source, dest, '1', [](auto& result) {});
3699
}
3700
3701
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_cond_no_then_recs_then")
3702
{
3703
const std::string source = R"(
3704
3705
)";
3706
3707
const std::string dest = R"(
3708
if x t@1
3709
)";
3710
3711
autocompleteFragmentInBothSolvers(
3712
source,
3713
dest,
3714
'1',
3715
[](auto& result)
3716
{
3717
REQUIRE(result.result);
3718
CHECK(result.result->acResults.entryMap.count("then"));
3719
}
3720
);
3721
}
3722
3723
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_then_recs_else")
3724
{
3725
const std::string source = R"(
3726
if x then
3727
3728
end
3729
)";
3730
3731
const std::string dest = R"(
3732
if x then
3733
e@1
3734
end
3735
)";
3736
3737
autocompleteFragmentInBothSolvers(
3738
source,
3739
dest,
3740
'1',
3741
[](auto& result)
3742
{
3743
REQUIRE(result.result);
3744
CHECK(result.result->acResults.entryMap.count("else"));
3745
CHECK(result.result->acResults.entryMap.count("elseif"));
3746
}
3747
);
3748
}
3749
3750
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_table_prop_recs_no_then")
3751
{
3752
const std::string source = R"(
3753
type T = {xa : number, y : number}
3754
local t : T = {xa = 3, y = 3}
3755
3756
if t.x then
3757
elseif
3758
end
3759
)";
3760
3761
const std::string dest = R"(
3762
type T = {xa : number, y : number}
3763
local t : T = {xa = 3, y = 3}
3764
3765
if t.x then
3766
elseif t.xa t@1
3767
end
3768
)";
3769
3770
autocompleteFragmentInBothSolvers(
3771
source,
3772
dest,
3773
'1',
3774
[](auto& result)
3775
{
3776
REQUIRE(result.result);
3777
CHECK(!result.result->acResults.entryMap.empty());
3778
CHECK(!result.result->acResults.entryMap.count("xa"));
3779
CHECK(!result.result->acResults.entryMap.count("y"));
3780
CHECK(result.result->acResults.entryMap.count("then"));
3781
}
3782
);
3783
}
3784
3785
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_table_prop_recs_with_then")
3786
{
3787
const std::string source = R"(
3788
type T = {xa : number, y : number}
3789
local t : T = {xa = 3, y = 3}
3790
3791
if t.x then
3792
elseif then
3793
end
3794
)";
3795
3796
const std::string dest = R"(
3797
type T = {xa : number, y : number}
3798
local t : T = {xa = 3, y = 3}
3799
3800
if t.x then
3801
elseif t.@1 then
3802
end
3803
)";
3804
3805
autocompleteFragmentInBothSolvers(
3806
source,
3807
dest,
3808
'1',
3809
[](auto& result)
3810
{
3811
REQUIRE(result.result);
3812
CHECK(!result.result->acResults.entryMap.empty());
3813
CHECK(result.result->acResults.entryMap.count("xa"));
3814
CHECK(result.result->acResults.entryMap.count("y"));
3815
CHECK(!result.result->acResults.entryMap.count("then"));
3816
}
3817
);
3818
}
3819
3820
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_old_solver")
3821
{
3822
const std::string source = R"(
3823
type Ok<T> = { type: "ok", value: T}
3824
type Err<E> = { type : "err", error : E}
3825
type Result<T,E> = Ok<T> | Err<E>
3826
3827
local result = {} :: Result<number, string>
3828
3829
if result.type == "ok" then
3830
3831
end
3832
)";
3833
3834
const std::string dest = R"(
3835
type Ok<T> = { type: "ok", value: T}
3836
type Err<E> = { type : "err", error : E}
3837
type Result<T,E> = Ok<T> | Err<E>
3838
3839
local result = {} :: Result<number, string>
3840
3841
if result.type == "ok" then
3842
result.@1
3843
end
3844
)";
3845
autocompleteFragmentInOldSolver(
3846
source,
3847
dest,
3848
'1',
3849
[](auto& result)
3850
{
3851
REQUIRE(result.result);
3852
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
3853
CHECK_EQ(result.result->acResults.entryMap.count("value"), 1);
3854
}
3855
);
3856
}
3857
3858
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_old_solver")
3859
{
3860
const std::string source = R"(
3861
type Ok<T> = { type: "ok", value: T}
3862
type Err<E> = { type : "err", error : E}
3863
type Result<T,E> = Ok<T> | Err<E>
3864
3865
local result = {} :: Result<number, string>
3866
3867
if result.type == "err" then
3868
3869
end
3870
)";
3871
3872
const std::string dest = R"(
3873
type Ok<T> = { type: "ok", value: T}
3874
type Err<E> = { type : "err", error : E}
3875
type Result<T,E> = Ok<T> | Err<E>
3876
3877
local result = {} :: Result<number, string>
3878
3879
if result.type == "err" then
3880
result.@1
3881
end
3882
)";
3883
3884
autocompleteFragmentInOldSolver(
3885
source,
3886
dest,
3887
'1',
3888
[](auto& result)
3889
{
3890
REQUIRE(result.result);
3891
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
3892
CHECK_EQ(result.result->acResults.entryMap.count("error"), 1);
3893
}
3894
);
3895
}
3896
3897
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_new_solver")
3898
{
3899
// TODO: CLI-155619 - Fragment autocomplete needs to use stale refinement information for modules typechecked in the new solver as well
3900
const std::string source = R"(
3901
type Ok<T> = { type: "ok", value: T}
3902
type Err<E> = { type : "err", error : E}
3903
type Result<T,E> = Ok<T> | Err<E>
3904
3905
local result = {} :: Result<number, string>
3906
3907
if result.type == "ok" then
3908
3909
end
3910
)";
3911
3912
const std::string dest = R"(
3913
type Ok<T> = { type: "ok", value: T}
3914
type Err<E> = { type : "err", error : E}
3915
type Result<T,E> = Ok<T> | Err<E>
3916
3917
local result = {} :: Result<number, string>
3918
3919
if result.type == "ok" then
3920
result.@1
3921
end
3922
)";
3923
autocompleteFragmentInNewSolver(
3924
source,
3925
dest,
3926
'1',
3927
[](auto& result)
3928
{
3929
REQUIRE(result.result);
3930
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
3931
CHECK_EQ(result.result->acResults.entryMap.count("value"), 1);
3932
}
3933
);
3934
}
3935
3936
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_new_solver")
3937
{
3938
const std::string source = R"(
3939
type Ok<T> = { type: "ok", value: T}
3940
type Err<E> = { type : "err", error : E}
3941
type Result<T,E> = Ok<T> | Err<E>
3942
3943
local result = {} :: Result<number, string>
3944
3945
if result.type == "err" then
3946
3947
end
3948
)";
3949
3950
const std::string dest = R"(
3951
type Ok<T> = { type: "ok", value: T}
3952
type Err<E> = { type : "err", error : E}
3953
type Result<T,E> = Ok<T> | Err<E>
3954
3955
local result = {} :: Result<number, string>
3956
3957
if result.type == "err" then
3958
result.@1
3959
end
3960
)";
3961
3962
autocompleteFragmentInNewSolver(
3963
source,
3964
dest,
3965
'1',
3966
[](auto& result)
3967
{
3968
REQUIRE(result.result);
3969
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
3970
CHECK_EQ(result.result->acResults.entryMap.count("error"), 1);
3971
}
3972
);
3973
}
3974
3975
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "inline_prop_read_on_requires_provides_results")
3976
{
3977
const std::string moduleA = R"(
3978
local mod = { prop1 = true}
3979
mod.prop2 = "a"
3980
function mod.foo(a: number)
3981
return a
3982
end
3983
return mod
3984
)";
3985
3986
const std::string mainModule = R"(
3987
3988
)";
3989
3990
fileResolver.source["MainModule"] = mainModule;
3991
fileResolver.source["MainModule/A"] = moduleA;
3992
getFrontend().check("MainModule/A", getOptions());
3993
getFrontend().check("MainModule", getOptions());
3994
3995
const std::string updatedMain = R"(
3996
require(script.A).
3997
)";
3998
3999
auto result = autocompleteFragment(updatedMain, Position{1, 18});
4000
CHECK(!result.result->acResults.entryMap.empty());
4001
CHECK(result.result->acResults.entryMap.count("prop1"));
4002
CHECK(result.result->acResults.entryMap.count("prop2"));
4003
CHECK(result.result->acResults.entryMap.count("foo"));
4004
}
4005
4006
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_types_provide_rich_autocomplete")
4007
{
4008
const std::string source = R"(
4009
type Service = {
4010
Start: (self: Service) -> (),
4011
Prop: number
4012
}
4013
4014
local Service: Service = {}
4015
4016
function Service:Start()
4017
4018
end
4019
)";
4020
const std::string dest = R"(
4021
type Service = {
4022
Start: (self: Service) -> (),
4023
Prop: number
4024
}
4025
4026
local Service: Service = {}
4027
4028
function Service:Start()
4029
self.@1
4030
end
4031
)";
4032
4033
autocompleteFragmentInBothSolvers(
4034
source,
4035
dest,
4036
'1',
4037
[](auto& result)
4038
{
4039
CHECK(!result.result->acResults.entryMap.empty());
4040
CHECK(result.result->acResults.entryMap.count("Prop"));
4041
CHECK(result.result->acResults.entryMap.count("Start"));
4042
}
4043
);
4044
}
4045
4046
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatable_setting_new_solver")
4047
{
4048
const std::string source = R"(
4049
type IAccount = {
4050
__index: IAccount,
4051
new : (string, number) -> Account,
4052
report: (self: Account) -> (),
4053
}
4054
4055
export type Account = setmetatable<{
4056
name: string,
4057
balance: number
4058
}, IAccount>;
4059
4060
local Account = {} :: IAccount
4061
Account.__index = Account
4062
4063
function Account.new(name, balance): Account
4064
local self = {}
4065
self.name = name
4066
self.balance = balance
4067
return setmetatable(self, Account)
4068
end
4069
4070
function Account:report()
4071
print("My balance is: " .. )
4072
end
4073
)";
4074
4075
const std::string dest = R"(
4076
type IAccount = {
4077
__index: IAccount,
4078
new : (string, number) -> Account,
4079
report: (self: Account) -> (),
4080
}
4081
4082
export type Account = setmetatable<{
4083
name: string,
4084
balance: number
4085
}, IAccount>;
4086
4087
local Account = {} :: IAccount
4088
Account.__index = Account
4089
4090
function Account.new(name, balance): Account
4091
local self = {}
4092
self.name = name
4093
self.balance = balance
4094
return setmetatable(self, Account)
4095
end
4096
4097
function Account:report()
4098
print("My balance is: " .. self.@1 )
4099
end
4100
)";
4101
4102
autocompleteFragmentInNewSolver(
4103
source,
4104
dest,
4105
'1',
4106
[](auto& result)
4107
{
4108
CHECK(!result.result->acResults.entryMap.empty());
4109
CHECK(result.result->acResults.entryMap.count("new"));
4110
CHECK(result.result->acResults.entryMap.count("report"));
4111
}
4112
);
4113
}
4114
4115
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_colon_good_recommendations")
4116
{
4117
const std::string source = R"(
4118
type Service = {
4119
Start: (self: Service) -> (),
4120
Prop: number
4121
}
4122
4123
local Service: Service = {}
4124
4125
function Service:Start()
4126
4127
end
4128
)";
4129
const std::string dest = R"(
4130
type Service = {
4131
Start: (self: Service) -> (),
4132
Prop: number
4133
}
4134
4135
local Service: Service = {}
4136
4137
function Service:Start()
4138
self:@1
4139
end
4140
)";
4141
4142
autocompleteFragmentInBothSolvers(
4143
source,
4144
dest,
4145
'1',
4146
[](auto& result)
4147
{
4148
CHECK(!result.result->acResults.entryMap.empty());
4149
CHECK(result.result->acResults.entryMap.count("Prop"));
4150
CHECK(result.result->acResults.entryMap["Prop"].wrongIndexType);
4151
CHECK(result.result->acResults.entryMap.count("Start"));
4152
CHECK(!result.result->acResults.entryMap["Start"].wrongIndexType);
4153
}
4154
);
4155
}
4156
4157
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "string_interpolation_format_provides_autocomplete_results")
4158
{
4159
const std::string source = R"(
4160
type Foo = {x : number, x1 : string, x2 : boolean}
4161
local e: Foo = {x = 1, x1 = "1", x2 = true}
4162
local s =
4163
)";
4164
4165
const std::string dest = R"(
4166
type Foo = {x : number, x1 : string, x2 : boolean}
4167
local e : Foo = {x = 1, x1 = "1", x2 = true}
4168
local s = `{e.@1 }`
4169
)";
4170
4171
autocompleteFragmentInBothSolvers(
4172
source,
4173
dest,
4174
'1',
4175
[](auto& result)
4176
{
4177
CHECK(!result.result->acResults.entryMap.empty());
4178
CHECK(result.result->acResults.entryMap.count("x"));
4179
CHECK(result.result->acResults.entryMap.count("x1"));
4180
CHECK(result.result->acResults.entryMap.count("x2"));
4181
}
4182
);
4183
}
4184
4185
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "string_interpolation_format_provides_results_inside_of_function_call")
4186
{
4187
const std::string source = R"(
4188
type T = {x : number, y : number, z : number}
4189
local e = {x = 1, y = 2, z = 3}
4190
print(`{e.x}`)
4191
)";
4192
const std::string dest = R"(
4193
type T = {x : number, y : number, z : number}
4194
local e = {x = 1, y = 2, z = 3}
4195
print(`{e.x} {e.@1}`)
4196
)";
4197
4198
autocompleteFragmentInBothSolvers(
4199
source,
4200
dest,
4201
'1',
4202
[](auto& result)
4203
{
4204
CHECK(!result.result->acResults.entryMap.empty());
4205
CHECK(result.result->acResults.entryMap.count("x"));
4206
CHECK(result.result->acResults.entryMap.count("y"));
4207
CHECK(result.result->acResults.entryMap.count("z"));
4208
}
4209
);
4210
}
4211
4212
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_in_should_rec")
4213
{
4214
const std::string source = R"(
4215
type T = { x : {[number] : number}, y: number}
4216
local x : T = ({} :: T)
4217
for _,n in pairs(x.@1) do
4218
end
4219
)";
4220
autocompleteFragmentInBothSolvers(
4221
source,
4222
source,
4223
'1',
4224
[](auto& result)
4225
{
4226
CHECK(!result.result->acResults.entryMap.empty());
4227
CHECK(result.result->acResults.entryMap.count("x"));
4228
CHECK(result.result->acResults.entryMap.count("y"));
4229
}
4230
);
4231
}
4232
4233
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_no_do")
4234
{
4235
const std::string source = R"(
4236
type T = { x : {[number] : number}, y: number, z: number}
4237
local x : T = ({} :: T)
4238
for i =
4239
end
4240
)";
4241
const std::string dest = R"(
4242
type T = { x : {[number] : number}, y: number, z : number}
4243
local x : T = ({} :: T)
4244
for i = x.@1
4245
end
4246
)";
4247
autocompleteFragmentInBothSolvers(
4248
source,
4249
dest,
4250
'1',
4251
[](auto& result)
4252
{
4253
CHECK(!result.result->acResults.entryMap.empty());
4254
CHECK(result.result->acResults.entryMap.count("x"));
4255
CHECK(result.result->acResults.entryMap.count("y"));
4256
CHECK(result.result->acResults.entryMap.count("z"));
4257
}
4258
);
4259
}
4260
4261
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_with_do_in_step")
4262
{
4263
const std::string source = R"(
4264
type T = { x : {[number] : number}, y: number, z: number}
4265
local x : T = ({} :: T)
4266
for i = x.y, 100 do
4267
end
4268
)";
4269
const std::string dest = R"(
4270
type T = { x : {[number] : number}, y: number, z : number}
4271
local x : T = ({} :: T)
4272
for i = x.y, 100, x.@1 do
4273
end
4274
)";
4275
autocompleteFragmentInBothSolvers(
4276
source,
4277
dest,
4278
'1',
4279
[](auto& result)
4280
{
4281
CHECK(!result.result->acResults.entryMap.empty());
4282
CHECK(result.result->acResults.entryMap.count("x"));
4283
CHECK(result.result->acResults.entryMap.count("y"));
4284
CHECK(result.result->acResults.entryMap.count("z"));
4285
}
4286
);
4287
}
4288
4289
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_with_do_in_max_delete")
4290
{
4291
const std::string source = R"(
4292
type T = { x : {[number] : number}, y: number, z: number}
4293
local x : T = ({} :: T)
4294
for i = x.y, x.z do
4295
end
4296
)";
4297
const std::string dest = R"(
4298
type T = { x : {[number] : number}, y: number, z : number}
4299
local x : T = ({} :: T)
4300
for i = x.y, x.@1 do
4301
end
4302
)";
4303
autocompleteFragmentInBothSolvers(
4304
source,
4305
dest,
4306
'1',
4307
[](auto& result)
4308
{
4309
CHECK(!result.result->acResults.entryMap.empty());
4310
CHECK(result.result->acResults.entryMap.count("x"));
4311
CHECK(result.result->acResults.entryMap.count("y"));
4312
CHECK(result.result->acResults.entryMap.count("z"));
4313
}
4314
);
4315
}
4316
4317
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_with_do_in_max_add")
4318
{
4319
const std::string source = R"(
4320
type T = { x : {[number] : number}, y: number, z: number}
4321
local x : T = ({} :: T)
4322
for i = x.y do
4323
end
4324
)";
4325
const std::string dest = R"(
4326
type T = { x : {[number] : number}, y: number, z : number}
4327
local x : T = ({} :: T)
4328
for i = x.y, x.@1 do
4329
end
4330
)";
4331
autocompleteFragmentInBothSolvers(
4332
source,
4333
dest,
4334
'1',
4335
[](auto& result)
4336
{
4337
CHECK(!result.result->acResults.entryMap.empty());
4338
CHECK(result.result->acResults.entryMap.count("x"));
4339
CHECK(result.result->acResults.entryMap.count("y"));
4340
CHECK(result.result->acResults.entryMap.count("z"));
4341
}
4342
);
4343
}
4344
4345
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "correctly_grab_innermost_refinement")
4346
{
4347
const std::string source = R"(
4348
--!strict
4349
type Type1 = { Type: "Type1", CommonKey: string, Type1Key: string }
4350
type Type2 = { Type: "Type2", CommonKey: string, Type2Key: string }
4351
type UnionType = Type1 | Type2
4352
4353
local foo: UnionType? = nil
4354
if foo then
4355
if foo.Type == "Type2" then
4356
end
4357
end
4358
)";
4359
4360
const std::string dest = R"(
4361
--!strict
4362
type Type1 = { Type: "Type1", CommonKey: string, Type1Key: string }
4363
type Type2 = { Type: "Type2", CommonKey: string, Type2Key: string }
4364
type UnionType = Type1 | Type2
4365
4366
local foo: UnionType? = nil
4367
if foo then
4368
if foo.Type == "Type2" then
4369
foo.@1
4370
end
4371
end
4372
)";
4373
4374
autocompleteFragmentInBothSolvers(
4375
source,
4376
dest,
4377
'1',
4378
[](FragmentAutocompleteStatusResult& result)
4379
{
4380
CHECK(!result.result->acResults.entryMap.empty());
4381
CHECK(result.result->acResults.entryMap.count("Type2Key") > 0);
4382
CHECK(result.result->acResults.entryMap.count("Type1Key") == 0);
4383
}
4384
);
4385
}
4386
4387
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "hot_comment_should_rec")
4388
{
4389
const std::string source = R"(--!@1)";
4390
autocompleteFragmentInBothSolvers(
4391
source,
4392
source,
4393
'1',
4394
[](auto& result)
4395
{
4396
CHECK(!result.result->acResults.entryMap.empty());
4397
CHECK(result.result->acResults.entryMap.count("strict"));
4398
CHECK(result.result->acResults.entryMap.count("nonstrict"));
4399
CHECK(result.result->acResults.entryMap.count("nocheck"));
4400
CHECK(result.result->acResults.entryMap.count("native"));
4401
CHECK(result.result->acResults.entryMap.count("nolint"));
4402
CHECK(result.result->acResults.entryMap.count("optimize"));
4403
}
4404
);
4405
}
4406
4407
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "len_operator_needs_to_provide_autocomplete_results")
4408
{
4409
std::string source = R"(
4410
type Pool = { numbers: { number }}
4411
4412
local function foobar(p)
4413
local pool = p :: Pool
4414
if #pool
4415
end
4416
)";
4417
std::string dest = R"(
4418
type Pool = { numbers: { number }}
4419
4420
local function foobar(p)
4421
local pool = p :: Pool
4422
if #pool.@1
4423
end
4424
)";
4425
autocompleteFragmentInBothSolvers(
4426
source,
4427
dest,
4428
'1',
4429
[](auto& result)
4430
{
4431
CHECK(!result.result->acResults.entryMap.empty());
4432
CHECK(result.result->acResults.entryMap.count("numbers"));
4433
}
4434
);
4435
}
4436
4437
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "unary_minus_operator_needs_to_provide_autocomplete_results")
4438
{
4439
std::string source = R"(
4440
type Pool = { x : number }
4441
4442
local function foobar(p)
4443
local pool = p :: Pool
4444
if -pool
4445
end
4446
)";
4447
std::string dest = R"(
4448
type Pool = { x : number }
4449
4450
local function foobar(p)
4451
local pool = p :: Pool
4452
if -pool.@1
4453
end
4454
)";
4455
autocompleteFragmentInBothSolvers(
4456
source,
4457
dest,
4458
'1',
4459
[](auto& result)
4460
{
4461
CHECK(!result.result->acResults.entryMap.empty());
4462
CHECK(result.result->acResults.entryMap.count("x"));
4463
}
4464
);
4465
}
4466
4467
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "method_in_unfinished_repeat_body_eof")
4468
{
4469
std::string source = R"(
4470
local t = {}
4471
function t:Foo() end
4472
repeat)";
4473
4474
std::string dest = R"(
4475
local t = {}
4476
function t:Foo() end
4477
repeat
4478
t:@1)";
4479
autocompleteFragmentInBothSolvers(
4480
source,
4481
dest,
4482
'1',
4483
[](auto& result)
4484
{
4485
CHECK(!result.result->acResults.entryMap.empty());
4486
CHECK(result.result->acResults.entryMap.count("Foo"));
4487
}
4488
);
4489
}
4490
4491
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "method_in_unfinished_repeat_body_not_eof")
4492
{
4493
std::string source = R"(
4494
local t = {}
4495
function t:Foo() end
4496
repeat
4497
t
4498
4499
local function whatever() end
4500
)";
4501
4502
std::string dest = R"(
4503
local t = {}
4504
function t:Foo() end
4505
repeat
4506
t:@1
4507
4508
local function whatever() end
4509
)";
4510
autocompleteFragmentInBothSolvers(
4511
source,
4512
dest,
4513
'1',
4514
[](auto& result)
4515
{
4516
CHECK(!result.result->acResults.entryMap.empty());
4517
CHECK(result.result->acResults.entryMap.count("Foo"));
4518
}
4519
);
4520
}
4521
4522
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "in_place_edit_of_for_loop_before_in_keyword_returns_fragment_starting_from_for")
4523
{
4524
std::string source = R"(
4525
local x = {}
4526
for i, value in x do
4527
print(i)
4528
end
4529
)";
4530
4531
std::string dest = R"(
4532
local x = {}
4533
for @1, value in x do
4534
print(i)
4535
end
4536
)";
4537
autocompleteFragmentInBothSolvers(
4538
source,
4539
dest,
4540
'1',
4541
[](auto& result)
4542
{
4543
CHECK(!result.result->acResults.entryMap.empty());
4544
}
4545
);
4546
}
4547
4548
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "bidirectionally_inferred_table_member")
4549
{
4550
std::string source = R"(
4551
type Foo = { foo1: string, bar1: number }
4552
type Bar = { foo2: boolean, bar2: string }
4553
type Baz = { foo3: number, bar3: boolean }
4554
4555
local X: Foo & Bar & Baz = {}
4556
)";
4557
4558
std::string dest = R"(
4559
type Foo = { foo1: string, bar1: number }
4560
type Bar = { foo2: boolean, bar2: string }
4561
type Baz = { foo3: number, bar3: boolean }
4562
4563
local X: Foo & Bar & Baz = { f@1 }
4564
4565
)";
4566
autocompleteFragmentInBothSolvers(
4567
source,
4568
dest,
4569
'1',
4570
[](auto& result)
4571
{
4572
CHECK(!result.result->acResults.entryMap.empty());
4573
CHECK(result.result->acResults.entryMap.count("foo1") > 0);
4574
CHECK(result.result->acResults.entryMap.count("foo2") > 0);
4575
CHECK(result.result->acResults.entryMap.count("foo3") > 0);
4576
}
4577
);
4578
}
4579
4580
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "oss_1850")
4581
{
4582
std::string source = R"(
4583
type t = { name: "t", } | { name: "ts", person: "dog" }
4584
4585
local t:t
4586
if t.name == "ts" then
4587
end
4588
)";
4589
std::string dest = R"(
4590
type t = { name: "t", } | { name: "ts", person: "dog" }
4591
4592
local t:t
4593
if t.name == "ts" then
4594
t.@1
4595
end
4596
)";
4597
4598
autocompleteFragmentInBothSolvers(
4599
source,
4600
dest,
4601
'1',
4602
[](auto& result)
4603
{
4604
CHECK(!result.result->acResults.entryMap.empty());
4605
CHECK(result.result->acResults.entryMap.count("name") > 0);
4606
CHECK(result.result->acResults.entryMap.count("person") > 0);
4607
}
4608
);
4609
}
4610
4611
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "anonymous_autofilled_generic_type_pack_vararg")
4612
{
4613
std::string source = R"(
4614
local function foo<A>(a: (...A) -> number, ...: A)
4615
return a(...)
4616
end
4617
)";
4618
4619
std::string dest = R"(
4620
local function foo<A>(a: (...A) -> number, ...: A)
4621
return a(...)
4622
end
4623
4624
foo(@1)
4625
)";
4626
4627
autocompleteFragmentInBothSolvers(
4628
source,
4629
dest,
4630
'1',
4631
[](FragmentAutocompleteStatusResult& frag)
4632
{
4633
const std::string EXPECTED_INSERT = "function(...): number end";
4634
REQUIRE(frag.result);
4635
REQUIRE(frag.result->acResults.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
4636
CHECK(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
4637
CHECK(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
4638
REQUIRE(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
4639
CHECK_EQ(EXPECTED_INSERT, *frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
4640
}
4641
);
4642
}
4643
4644
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "anonymous_autofilled_generic_named_arg")
4645
{
4646
std::string source = R"(
4647
local function foo<A>(f: (a: A) -> number, a: A)
4648
return f(a)
4649
end
4650
)";
4651
4652
std::string dest = R"(
4653
local function foo<A>(f: (a: A) -> number, a: A)
4654
return f(a)
4655
end
4656
4657
foo(@1)
4658
)";
4659
4660
autocompleteFragmentInBothSolvers(
4661
source,
4662
dest,
4663
'1',
4664
[](FragmentAutocompleteStatusResult& frag)
4665
{
4666
const std::string EXPECTED_INSERT = "function(a): number end";
4667
REQUIRE(frag.result);
4668
REQUIRE(frag.result->acResults.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
4669
CHECK(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
4670
CHECK(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
4671
REQUIRE(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
4672
CHECK_EQ(EXPECTED_INSERT, *frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
4673
}
4674
);
4675
}
4676
4677
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "anonymous_autofilled_generic_return_type")
4678
{
4679
std::string source = R"(
4680
local function foo<A>(f: () -> A)
4681
return f()
4682
end
4683
)";
4684
4685
std::string dest = R"(
4686
local function foo<A>(f: () -> A)
4687
return f()
4688
end
4689
4690
foo(@1)
4691
)";
4692
4693
autocompleteFragmentInBothSolvers(
4694
source,
4695
dest,
4696
'1',
4697
[](FragmentAutocompleteStatusResult& frag)
4698
{
4699
const std::string EXPECTED_INSERT = "function() end";
4700
REQUIRE(frag.result);
4701
REQUIRE(frag.result->acResults.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
4702
CHECK(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
4703
CHECK(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
4704
REQUIRE(frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
4705
CHECK_EQ(EXPECTED_INSERT, *frag.result->acResults.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
4706
}
4707
);
4708
}
4709
4710
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_using_indexer_with_singleton_keys")
4711
{
4712
std::string source = R"(
4713
type List = "Val1" | "Val2" | "Val3"
4714
local Table: { [List]: boolean }
4715
)";
4716
4717
std::string dest = R"(
4718
type List = "Val1" | "Val2" | "Val3"
4719
local Table: { [List]: boolean }
4720
local _ = Table.@1
4721
)";
4722
4723
autocompleteFragmentInBothSolvers(
4724
source,
4725
dest,
4726
'1',
4727
[](FragmentAutocompleteStatusResult& frag)
4728
{
4729
REQUIRE(frag.result);
4730
CHECK(frag.result->acResults.entryMap.count("Val1") > 0);
4731
CHECK(frag.result->acResults.entryMap.count("Val2") > 0);
4732
CHECK(frag.result->acResults.entryMap.count("Val3") > 0);
4733
}
4734
);
4735
}
4736
4737
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_using_function_call_with_variadic_args")
4738
{
4739
ScopedFastFlag sff{FFlag::LuauAutocompleteFunctionCallArgTails2, true};
4740
4741
std::string source = R"(
4742
local function foo(...: "Val1" | "Val2") end
4743
)";
4744
4745
std::string dest = R"(
4746
local function foo(...: "Val1" | "Val2") end
4747
foo(@1
4748
)";
4749
4750
autocompleteFragmentInBothSolvers(
4751
source,
4752
dest,
4753
'1',
4754
[](FragmentAutocompleteStatusResult& frag)
4755
{
4756
REQUIRE(frag.result);
4757
CHECK(frag.result->acResults.entryMap.count("\"Val1\"") == 1);
4758
CHECK(frag.result->acResults.entryMap.count("\"Val2\"") == 1);
4759
}
4760
);
4761
}
4762
4763
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "fragment_autocomplete_table_insert")
4764
{
4765
ScopedFastFlag sffs[] = {
4766
{FFlag::LuauOverloadGetsInstantiated, true},
4767
{FFlag::LuauReplacerRespectsReboundGenerics, true},
4768
};
4769
4770
std::string src = R"(
4771
local function addToTable(t: {{ foobar: number }})
4772
table.insert(t, {})
4773
end
4774
)";
4775
4776
std::string dest = R"(
4777
local function addToTable(t: {{ foobar: number }})
4778
table.insert(t, { f@1 })
4779
end
4780
)";
4781
4782
autocompleteFragmentInBothSolvers(
4783
src,
4784
dest,
4785
'1',
4786
[](auto& ac)
4787
{
4788
REQUIRE(ac.result);
4789
CHECK(ac.result->acResults.entryMap.count("foobar") > 0);
4790
}
4791
);
4792
}
4793
4794
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_react_properties")
4795
{
4796
ScopedFastFlag sffs[] = {
4797
{FFlag::LuauOverloadGetsInstantiated, true},
4798
{FFlag::LuauReplacerRespectsReboundGenerics, true},
4799
{FFlag::LuauUnifier2HandleMismatchedPacks2, true},
4800
};
4801
4802
std::string src = R"(
4803
type React_Node = any
4804
type ReactElement<P, T> = any
4805
4806
type React_StatelessFunctionalComponent<Props> = (props: Props, context: any) -> React_Node
4807
type React_Component<Props, State = nil> = {}
4808
type createElementFn = <P, T>(
4809
type_:
4810
| React_StatelessFunctionalComponent<P>
4811
| React_Component<P>
4812
| string,
4813
props: P?,
4814
...(React_Node | (...any) -> React_Node)
4815
) -> ReactElement<P, T>
4816
4817
local createElement: createElementFn = nil :: any
4818
4819
local function MyComponent(props: { foobar: string, barbaz: { bazquxx: string } })
4820
return nil
4821
end
4822
4823
)";
4824
4825
std::string dest = R"(
4826
type React_Node = any
4827
type ReactElement<P, T> = any
4828
4829
type React_StatelessFunctionalComponent<Props> = (props: Props, context: any) -> React_Node
4830
type React_Component<Props, State = nil> = {}
4831
type createElementFn = <P, T>(
4832
type_:
4833
| React_StatelessFunctionalComponent<P>
4834
| React_Component<P>
4835
| string,
4836
props: P?,
4837
...(React_Node | (...any) -> React_Node)
4838
) -> ReactElement<P, T>
4839
4840
local createElement: createElementFn = nil :: any
4841
4842
local function MyComponent(props: { foobar: string, barbaz: { bazquxx: string } })
4843
return nil
4844
end
4845
4846
createElement(MyComponent, { f@1 })
4847
createElement(MyComponent, { barbaz = { b@2 } })
4848
createElement(MyComponent, { foobar = {}, b@3 })
4849
)";
4850
4851
autocompleteFragmentInBothSolvers(
4852
src,
4853
dest,
4854
'1',
4855
[](auto& ac)
4856
{
4857
REQUIRE(ac.result);
4858
CHECK(ac.result->acResults.entryMap.count("foobar") > 0);
4859
}
4860
);
4861
4862
autocompleteFragmentInBothSolvers(
4863
src,
4864
dest,
4865
'2',
4866
[](auto& ac)
4867
{
4868
REQUIRE(ac.result);
4869
CHECK(ac.result->acResults.entryMap.count("bazquxx") > 0);
4870
}
4871
);
4872
4873
autocompleteFragmentInBothSolvers(
4874
src,
4875
dest,
4876
'3',
4877
[](auto& ac)
4878
{
4879
REQUIRE(ac.result);
4880
CHECK(ac.result->acResults.entryMap.count("barbaz") > 0);
4881
}
4882
);
4883
}
4884
4885
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_react_narrow_fragment")
4886
{
4887
ScopedFastFlag sffs[] = {
4888
{FFlag::LuauOverloadGetsInstantiated, true},
4889
{FFlag::LuauReplacerRespectsReboundGenerics, true},
4890
{FFlag::LuauUnifier2HandleMismatchedPacks2, true},
4891
};
4892
4893
std::string src = R"(
4894
type React_Node = any
4895
type ReactElement<P, T> = any
4896
4897
type React_StatelessFunctionalComponent<Props> = (props: Props, context: any) -> React_Node
4898
type React_Component<Props, State = nil> = {}
4899
type createElementFn = <P, T>(
4900
type_:
4901
| React_StatelessFunctionalComponent<P>
4902
| React_Component<P>
4903
| string,
4904
props: P?,
4905
...(React_Node | (...any) -> React_Node)
4906
) -> ReactElement<P, T>
4907
4908
local createElement: createElementFn = nil :: any
4909
4910
local function MyComponent(props: { foobar: string, barbaz: { bazquxx: string } })
4911
return nil
4912
end
4913
4914
createElement(MyComponent, { })
4915
)";
4916
4917
std::string dest = R"(
4918
type React_Node = any
4919
type ReactElement<P, T> = any
4920
4921
type React_StatelessFunctionalComponent<Props> = (props: Props, context: any) -> React_Node
4922
type React_Component<Props, State = nil> = {}
4923
type createElementFn = <P, T>(
4924
type_:
4925
| React_StatelessFunctionalComponent<P>
4926
| React_Component<P>
4927
| string,
4928
props: P?,
4929
...(React_Node | (...any) -> React_Node)
4930
) -> ReactElement<P, T>
4931
4932
local createElement: createElementFn = nil :: any
4933
4934
local function MyComponent(props: { foobar: string, barbaz: { bazquxx: string } })
4935
return nil
4936
end
4937
4938
createElement(MyComponent, { f@1 })
4939
)";
4940
4941
autocompleteFragmentInBothSolvers(
4942
src,
4943
dest,
4944
'1',
4945
[](auto& ac)
4946
{
4947
REQUIRE(ac.result);
4948
CHECK(ac.result->acResults.entryMap.count("foobar") > 0);
4949
}
4950
);
4951
}
4952
4953
// NOLINTEND(bugprone-unchecked-optional-access)
4954
4955
TEST_SUITE_END();
4956
4957