Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/AstQuery.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/AstQuery.h"
4
5
#include "AstQueryDsl.h"
6
#include "doctest.h"
7
#include "Fixture.h"
8
9
using namespace Luau;
10
11
struct DocumentationSymbolFixture : BuiltinsFixture
12
{
13
std::optional<DocumentationSymbol> getDocSymbol(const std::string& source, Position position)
14
{
15
check(source);
16
17
SourceModule* sourceModule = getMainSourceModule();
18
ModulePtr module = getMainModule();
19
20
return getDocumentationSymbolAtPosition(*sourceModule, *module, position);
21
}
22
};
23
24
TEST_SUITE_BEGIN("AstQuery::getDocumentationSymbolAtPosition");
25
26
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "binding")
27
{
28
std::optional<DocumentationSymbol> global = getDocSymbol(
29
R"(
30
local a = string.sub()
31
)",
32
Position(1, 21)
33
);
34
35
CHECK_EQ(global, "@luau/global/string");
36
}
37
38
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "prop")
39
{
40
std::optional<DocumentationSymbol> substring = getDocSymbol(
41
R"(
42
local a = string.sub()
43
)",
44
Position(1, 27)
45
);
46
47
CHECK_EQ(substring, "@luau/global/string.sub");
48
}
49
50
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "event_callback_arg")
51
{
52
loadDefinition(R"(
53
declare function Connect(fn: (string) -> ())
54
)");
55
56
std::optional<DocumentationSymbol> substring = getDocSymbol(
57
R"(
58
Connect(function(abc)
59
end)
60
)",
61
Position(1, 27)
62
);
63
64
CHECK_EQ(substring, "@test/global/Connect/param/0/param/0");
65
}
66
67
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_fn")
68
{
69
loadDefinition(R"(
70
declare foo: ((string) -> number) & ((number) -> string)
71
)");
72
73
std::optional<DocumentationSymbol> symbol = getDocSymbol(
74
R"(
75
foo("asdf")
76
)",
77
Position(1, 10)
78
);
79
80
CHECK_EQ(symbol, "@test/global/foo/overload/(string) -> number");
81
}
82
83
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "class_method")
84
{
85
loadDefinition(R"(
86
declare class Foo
87
function bar(self, x: string): number
88
end
89
90
declare Foo: {
91
new: () -> Foo
92
}
93
)");
94
95
std::optional<DocumentationSymbol> symbol = getDocSymbol(
96
R"(
97
local x: Foo = Foo.new()
98
x:bar("asdf")
99
)",
100
Position(2, 11)
101
);
102
103
CHECK_EQ(symbol, "@test/globaltype/Foo.bar");
104
}
105
106
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_class_method")
107
{
108
loadDefinition(R"(
109
declare class Foo
110
function bar(self, x: string): number
111
function bar(self, x: number): string
112
end
113
114
declare Foo: {
115
new: () -> Foo
116
}
117
)");
118
119
std::optional<DocumentationSymbol> symbol = getDocSymbol(
120
R"(
121
local x: Foo = Foo.new()
122
x:bar("asdf")
123
)",
124
Position(2, 11)
125
);
126
127
CHECK_EQ(symbol, "@test/globaltype/Foo.bar/overload/(Foo, string) -> number");
128
}
129
130
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_function_prop")
131
{
132
loadDefinition(R"(
133
declare Foo: {
134
new: (number) -> string
135
}
136
)");
137
138
std::optional<DocumentationSymbol> symbol = getDocSymbol(
139
R"(
140
Foo.new("asdf")
141
)",
142
Position(1, 13)
143
);
144
145
CHECK_EQ(symbol, "@test/global/Foo.new");
146
}
147
148
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop")
149
{
150
loadDefinition(R"(
151
declare Foo: {
152
new: ((number) -> string) & ((string) -> number)
153
}
154
)");
155
156
std::optional<DocumentationSymbol> symbol = getDocSymbol(
157
R"(
158
Foo.new("asdf")
159
)",
160
Position(1, 13)
161
);
162
163
CHECK_EQ(symbol, "@test/global/Foo.new/overload/(string) -> number");
164
}
165
166
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method")
167
{
168
std::optional<DocumentationSymbol> symbol = getDocSymbol(
169
R"(
170
local x: string = "Foo"
171
x:rep(2)
172
)",
173
Position(2, 12)
174
);
175
176
CHECK_EQ(symbol, "@luau/global/string.rep");
177
}
178
179
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "parent_class_method")
180
{
181
loadDefinition(R"(
182
declare class Foo
183
function bar(self, x: string): number
184
end
185
186
declare class Bar extends Foo
187
function notbar(self, x: string): number
188
end
189
)");
190
191
std::optional<DocumentationSymbol> symbol = getDocSymbol(
192
R"(
193
local x: Bar = Bar.new()
194
x:bar("asdf")
195
)",
196
Position(2, 11)
197
);
198
199
CHECK_EQ(symbol, "@test/globaltype/Foo.bar");
200
}
201
202
TEST_SUITE_END();
203
204
TEST_SUITE_BEGIN("AstQuery");
205
206
TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type")
207
{
208
DOES_NOT_PASS_NEW_SOLVER_GUARD();
209
210
check(R"(
211
local function foo() return 2 end
212
local function bar(a: number) return -a end
213
bar(foo())
214
)");
215
216
auto oty = findTypeAtPosition(Position(3, 7));
217
REQUIRE(oty);
218
CHECK_EQ("number", toString(*oty));
219
220
auto expectedOty = findExpectedTypeAtPosition(Position(3, 7));
221
REQUIRE(expectedOty);
222
CHECK_EQ("number", toString(*expectedOty));
223
}
224
225
TEST_CASE_FIXTURE(Fixture, "ast_ancestry_at_eof")
226
{
227
check(R"(
228
if true then
229
)");
230
231
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(*getMainSourceModule(), Position(2, 4));
232
REQUIRE_GE(ancestry.size(), 2);
233
AstStat* parentStat = ancestry[ancestry.size() - 2]->asStat();
234
REQUIRE(bool(parentStat));
235
REQUIRE(parentStat->is<AstStatIf>());
236
}
237
238
TEST_CASE_FIXTURE(Fixture, "ac_ast_ancestry_at_number_const")
239
{
240
check(R"(
241
print(3.)
242
)");
243
244
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(*getMainSourceModule(), Position(1, 8));
245
REQUIRE_GE(ancestry.size(), 2);
246
REQUIRE(ancestry.back()->is<AstExprConstantNumber>());
247
}
248
249
TEST_CASE_FIXTURE(Fixture, "ac_ast_ancestry_in_workspace_dot")
250
{
251
check(R"(
252
print(workspace.)
253
)");
254
255
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(*getMainSourceModule(), Position(1, 16));
256
REQUIRE_GE(ancestry.size(), 2);
257
REQUIRE(ancestry.back()->is<AstExprIndexName>());
258
}
259
260
TEST_CASE_FIXTURE(Fixture, "ac_ast_ancestry_in_workspace_colon")
261
{
262
check(R"(
263
print(workspace:)
264
)");
265
266
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(*getMainSourceModule(), Position(1, 16));
267
REQUIRE_GE(ancestry.size(), 2);
268
REQUIRE(ancestry.back()->is<AstExprIndexName>());
269
}
270
271
TEST_CASE_FIXTURE(Fixture, "Luau_query")
272
{
273
AstStatBlock* block = parse(R"(
274
if true then
275
end
276
)");
277
278
AstStatIf* if_ = Luau::query<AstStatIf>(block);
279
CHECK(if_);
280
}
281
282
TEST_CASE_FIXTURE(Fixture, "Luau_query_for_2nd_if_stat_which_doesnt_exist")
283
{
284
AstStatBlock* block = parse(R"(
285
if true then
286
end
287
)");
288
289
AstStatIf* if_ = Luau::query<AstStatIf, 2>(block);
290
CHECK(!if_);
291
}
292
293
TEST_CASE_FIXTURE(Fixture, "Luau_nested_query")
294
{
295
AstStatBlock* block = parse(R"(
296
if true then
297
end
298
)");
299
300
AstStatIf* if_ = Luau::query<AstStatIf>(block);
301
REQUIRE(if_);
302
AstExprConstantBool* bool_ = Luau::query<AstExprConstantBool>(if_);
303
REQUIRE(bool_);
304
}
305
306
TEST_CASE_FIXTURE(Fixture, "Luau_nested_query_but_first_query_failed")
307
{
308
AstStatBlock* block = parse(R"(
309
if true then
310
end
311
)");
312
313
AstStatIf* if_ = Luau::query<AstStatIf, 2>(block);
314
REQUIRE(!if_);
315
AstExprConstantBool* bool_ = Luau::query<AstExprConstantBool>(if_); // ensure it doesn't crash
316
REQUIRE(!bool_);
317
}
318
319
TEST_CASE_FIXTURE(Fixture, "Luau_selectively_query_for_a_different_boolean")
320
{
321
AstStatBlock* block = parse(R"(
322
local x = false and true
323
local y = true and false
324
)");
325
326
AstExprConstantBool* fst = Luau::query<AstExprConstantBool>(block, {nth<AstStatLocal>(), nth<AstExprConstantBool>(2)});
327
REQUIRE(fst);
328
REQUIRE(fst->value == true);
329
330
AstExprConstantBool* snd = Luau::query<AstExprConstantBool>(block, {nth<AstStatLocal>(2), nth<AstExprConstantBool>(2)});
331
REQUIRE(snd);
332
REQUIRE(snd->value == false);
333
}
334
335
TEST_CASE_FIXTURE(Fixture, "Luau_selectively_query_for_a_different_boolean_2")
336
{
337
AstStatBlock* block = parse(R"(
338
local x = false and true
339
local y = true and false
340
)");
341
342
AstExprConstantBool* snd = Luau::query<AstExprConstantBool>(block, {nth<AstStatLocal>(2), nth<AstExprConstantBool>()});
343
REQUIRE(snd);
344
REQUIRE(snd->value == true);
345
}
346
347
TEST_CASE_FIXTURE(Fixture, "include_types_ancestry")
348
{
349
check("local x: number = 4;");
350
const Position pos(0, 10);
351
352
std::vector<AstNode*> ancestryNoTypes = findAstAncestryOfPosition(*getMainSourceModule(), pos);
353
std::vector<AstNode*> ancestryTypes = findAstAncestryOfPosition(*getMainSourceModule(), pos, true);
354
355
CHECK(ancestryTypes.size() > ancestryNoTypes.size());
356
CHECK(!ancestryNoTypes.back()->asType());
357
CHECK(ancestryTypes.back()->asType());
358
}
359
360
TEST_CASE_FIXTURE(Fixture, "find_name_ancestry")
361
{
362
check(R"(
363
local tbl = {}
364
function tbl:abc() end
365
)");
366
const Position pos(2, 18);
367
368
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(*getMainSourceModule(), pos);
369
370
REQUIRE(!ancestry.empty());
371
CHECK(ancestry.back()->is<AstExprLocal>());
372
}
373
374
TEST_CASE_FIXTURE(Fixture, "find_expr_ancestry")
375
{
376
check(R"(
377
local tbl = {}
378
function tbl:abc() end
379
)");
380
const Position pos(2, 29);
381
382
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(*getMainSourceModule(), pos);
383
384
REQUIRE(!ancestry.empty());
385
CHECK(ancestry.back()->is<AstExprFunction>());
386
}
387
388
TEST_CASE_FIXTURE(BuiltinsFixture, "find_binding_at_position_global_start_of_file")
389
{
390
391
check("local x = string.char(1)");
392
const Position pos(0, 12);
393
394
std::optional<Binding> binding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), pos);
395
396
REQUIRE(binding);
397
CHECK_EQ(binding->location, Location{Position{0, 0}, Position{0, 0}});
398
}
399
400
TEST_CASE_FIXTURE(Fixture, "interior_binding_location_is_consistent_with_exterior_binding")
401
{
402
CheckResult result = check(R"(
403
local function abcd(arg)
404
abcd(arg)
405
end
406
407
abcd(0)
408
)");
409
410
LUAU_REQUIRE_NO_ERRORS(result);
411
412
std::optional<Binding> declBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {1, 26});
413
REQUIRE(declBinding);
414
415
CHECK(declBinding->location == Location{{1, 23}, {1, 27}});
416
417
std::optional<Binding> innerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {2, 15});
418
REQUIRE(innerCallBinding);
419
420
CHECK(innerCallBinding->location == Location{{1, 23}, {1, 27}});
421
422
std::optional<Binding> outerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {5, 8});
423
REQUIRE(outerCallBinding);
424
425
CHECK(outerCallBinding->location == Location{{1, 23}, {1, 27}});
426
}
427
428
TEST_SUITE_END();
429
430