Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Require/src/RequireNavigator.cpp
2725 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/RequireNavigator.h"
4
5
#include "AliasCycleTracker.h"
6
#include "PathUtilities.h"
7
8
#include "Luau/Common.h"
9
#include "Luau/Config.h"
10
#include "Luau/LuauConfig.h"
11
12
#include <algorithm>
13
#include <optional>
14
#include <utility>
15
16
LUAU_FASTFLAGVARIABLE(LuauRequireAliasOverrideOrderFix)
17
18
namespace Luau::Require
19
{
20
21
using Error = std::optional<std::string>;
22
23
static std::string extractAlias(std::string_view path)
24
{
25
// To ignore the '@' alias prefix when processing the alias
26
const size_t aliasStartPos = 1;
27
28
// If a directory separator was found, the length of the alias is the
29
// distance between the start of the alias and the separator. Otherwise,
30
// the whole string after the alias symbol is the alias.
31
size_t aliasLen = path.find_first_of('/');
32
if (aliasLen != std::string::npos)
33
aliasLen -= aliasStartPos;
34
35
return std::string{path.substr(aliasStartPos, aliasLen)};
36
}
37
38
Navigator::Navigator(NavigationContext& navigationContext, ErrorHandler& errorHandler)
39
: navigationContext(navigationContext)
40
, errorHandler(errorHandler)
41
{
42
}
43
44
Navigator::Status Navigator::navigate(std::string path)
45
{
46
std::replace(path.begin(), path.end(), '\\', '/');
47
48
if (Error error = navigateImpl(path))
49
{
50
errorHandler.reportError(*error);
51
return Status::ErrorReported;
52
}
53
54
return Status::Success;
55
}
56
57
Error Navigator::navigateImpl(std::string_view path)
58
{
59
PathType pathType = getPathType(path);
60
61
if (pathType == PathType::Unsupported)
62
return "require path must start with a valid prefix: ./, ../, or @";
63
64
if (pathType == PathType::Aliased)
65
{
66
std::string alias = extractAlias(path);
67
std::transform(
68
alias.begin(),
69
alias.end(),
70
alias.begin(),
71
[](unsigned char c)
72
{
73
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
74
}
75
);
76
77
if (FFlag::LuauRequireAliasOverrideOrderFix)
78
{
79
if (Error error = resetToRequirer())
80
return error;
81
}
82
83
if (auto [error, wasOverridden] = toAliasOverride(alias); error)
84
{
85
return error;
86
}
87
else if (wasOverridden)
88
{
89
if (Error error = navigateThroughPath(path))
90
return error;
91
92
return std::nullopt;
93
}
94
95
if (!FFlag::LuauRequireAliasOverrideOrderFix)
96
{
97
if (Error error = resetToRequirer())
98
return error;
99
}
100
101
Config config;
102
if (Error error = navigateToAndPopulateConfig(alias, config))
103
return error;
104
105
if (config.aliases.contains(alias))
106
{
107
if (Error error = navigateToAlias(alias, config, {}))
108
return error;
109
if (Error error = navigateThroughPath(path))
110
return error;
111
112
return std::nullopt;
113
}
114
else
115
{
116
if (alias == "self")
117
{
118
// If the alias is "@self", we reset to the requirer's context and
119
// navigate directly from there.
120
if (Error error = resetToRequirer())
121
return error;
122
if (Error error = navigateThroughPath(path))
123
return error;
124
125
return std::nullopt;
126
}
127
128
if (Error error = toAliasFallback(alias))
129
return error;
130
if (Error error = navigateThroughPath(path))
131
return error;
132
133
return std::nullopt;
134
}
135
}
136
137
if (pathType == PathType::RelativeToCurrent || pathType == PathType::RelativeToParent)
138
{
139
if (Error error = resetToRequirer())
140
return error;
141
if (Error error = navigateToParent(std::nullopt))
142
return error;
143
if (Error error = navigateThroughPath(path))
144
return error;
145
}
146
147
return std::nullopt;
148
}
149
150
Error Navigator::navigateThroughPath(std::string_view path)
151
{
152
std::pair<std::string_view, std::string_view> components = splitPath(path);
153
if (path.size() >= 1 && path[0] == '@')
154
{
155
// If the path is aliased, we ignore the alias: this function assumes
156
// that navigation to an alias is handled by the caller.
157
components = splitPath(components.second);
158
}
159
160
std::optional<std::string> previousComponent;
161
while (!(components.first.empty() && components.second.empty()))
162
{
163
if (components.first == "." || components.first.empty())
164
{
165
components = splitPath(components.second);
166
continue;
167
}
168
else if (components.first == "..")
169
{
170
if (Error error = navigateToParent(previousComponent))
171
return error;
172
}
173
else
174
{
175
if (Error error = navigateToChild(std::string{components.first}))
176
return error;
177
}
178
previousComponent = components.first;
179
components = splitPath(components.second);
180
}
181
182
return std::nullopt;
183
}
184
185
Error Navigator::navigateToAlias(const std::string& alias, const Config& config, AliasCycleTracker cycleTracker)
186
{
187
LUAU_ASSERT(config.aliases.contains(alias));
188
std::string value = config.aliases.find(alias)->value;
189
PathType pathType = getPathType(value);
190
191
if (pathType == PathType::RelativeToCurrent || pathType == PathType::RelativeToParent)
192
{
193
if (Error error = navigateThroughPath(value))
194
return error;
195
}
196
else if (pathType == PathType::Aliased)
197
{
198
if (Error error = cycleTracker.add(alias))
199
return error;
200
201
std::string nextAlias = extractAlias(value);
202
203
if (auto [error, wasOverridden] = toAliasOverride(nextAlias); error)
204
{
205
return error;
206
}
207
else if (wasOverridden)
208
{
209
if (Error error = navigateThroughPath(value))
210
return error;
211
212
return std::nullopt;
213
}
214
215
if (config.aliases.contains(nextAlias))
216
{
217
if (Error error = navigateToAlias(nextAlias, config, std::move(cycleTracker)))
218
return error;
219
}
220
else
221
{
222
Config parentConfig;
223
if (Error error = navigateToAndPopulateConfig(nextAlias, parentConfig))
224
return error;
225
226
if (parentConfig.aliases.contains(nextAlias))
227
{
228
if (Error error = navigateToAlias(nextAlias, parentConfig, {}))
229
return error;
230
}
231
else
232
{
233
if (Error error = toAliasFallback(nextAlias))
234
return error;
235
}
236
}
237
238
if (Error error = navigateThroughPath(value))
239
return error;
240
}
241
else
242
{
243
if (Error error = jumpToAlias(value))
244
return error;
245
}
246
247
return std::nullopt;
248
}
249
250
Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias, Config& config)
251
{
252
while (!config.aliases.contains(desiredAlias))
253
{
254
config = {}; // Clear existing config data.
255
256
NavigationContext::NavigateResult result = navigationContext.toParent();
257
if (result == NavigationContext::NavigateResult::Ambiguous)
258
return "could not navigate up the ancestry chain during search for alias \"" + desiredAlias + "\" (ambiguous)";
259
if (result == NavigationContext::NavigateResult::NotFound)
260
break; // Not treated as an error: interpreted as reaching the root.
261
262
NavigationContext::ConfigStatus status = navigationContext.getConfigStatus();
263
if (status == NavigationContext::ConfigStatus::Absent)
264
{
265
continue;
266
}
267
else if (status == NavigationContext::ConfigStatus::Ambiguous)
268
{
269
return "could not resolve alias \"" + desiredAlias + "\" (ambiguous configuration file)";
270
}
271
else
272
{
273
if (navigationContext.getConfigBehavior() == NavigationContext::ConfigBehavior::GetAlias)
274
{
275
config.setAlias(desiredAlias, *navigationContext.getAlias(desiredAlias), /* configLocation = */ "unused");
276
break;
277
}
278
279
std::optional<std::string> configContents = navigationContext.getConfig();
280
if (!configContents)
281
return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\"";
282
283
Luau::ConfigOptions opts;
284
Luau::ConfigOptions::AliasOptions aliasOpts;
285
aliasOpts.configLocation = "unused";
286
aliasOpts.overwriteAliases = false;
287
opts.aliasOptions = std::move(aliasOpts);
288
289
if (status == NavigationContext::ConfigStatus::PresentJson)
290
{
291
if (Error error = Luau::parseConfig(*configContents, config, opts))
292
return error;
293
}
294
else if (status == NavigationContext::ConfigStatus::PresentLuau)
295
{
296
InterruptCallbacks callbacks;
297
callbacks.initCallback = navigationContext.luauConfigInit;
298
callbacks.interruptCallback = navigationContext.luauConfigInterrupt;
299
300
if (Error error = Luau::extractLuauConfig(*configContents, config, std::move(opts.aliasOptions), std::move(callbacks)))
301
return error;
302
}
303
}
304
};
305
306
return std::nullopt;
307
}
308
309
Error Navigator::resetToRequirer()
310
{
311
NavigationContext::NavigateResult result = navigationContext.resetToRequirer();
312
if (result == NavigationContext::NavigateResult::Success)
313
return std::nullopt;
314
315
std::string errorMessage = "could not reset to requiring context";
316
if (result == NavigationContext::NavigateResult::Ambiguous)
317
errorMessage += " (ambiguous)";
318
return errorMessage;
319
}
320
321
Error Navigator::jumpToAlias(const std::string& aliasPath)
322
{
323
NavigationContext::NavigateResult result = navigationContext.jumpToAlias(aliasPath);
324
if (result == NavigationContext::NavigateResult::Success)
325
return std::nullopt;
326
327
std::string errorMessage = "could not jump to alias \"" + aliasPath + "\"";
328
if (result == NavigationContext::NavigateResult::Ambiguous)
329
errorMessage += " (ambiguous)";
330
return errorMessage;
331
}
332
333
Error Navigator::navigateToParent(std::optional<std::string> previousComponent)
334
{
335
NavigationContext::NavigateResult result = navigationContext.toParent();
336
if (result == NavigationContext::NavigateResult::Success)
337
return std::nullopt;
338
339
std::string errorMessage;
340
if (previousComponent)
341
errorMessage = "could not get parent of component \"" + *previousComponent + "\"";
342
else
343
errorMessage = "could not get parent of requiring context";
344
if (result == NavigationContext::NavigateResult::Ambiguous)
345
errorMessage += " (ambiguous)";
346
return errorMessage;
347
}
348
349
Error Navigator::navigateToChild(const std::string& component)
350
{
351
NavigationContext::NavigateResult result = navigationContext.toChild(component);
352
if (result == NavigationContext::NavigateResult::Success)
353
return std::nullopt;
354
355
std::string errorMessage = "could not resolve child component \"" + component + "\"";
356
if (result == NavigationContext::NavigateResult::Ambiguous)
357
errorMessage += " (ambiguous)";
358
return errorMessage;
359
}
360
361
std::pair<Error, bool> Navigator::toAliasOverride(const std::string& aliasUnprefixed)
362
{
363
std::pair<Error, bool> result;
364
switch (navigationContext.toAliasOverride(aliasUnprefixed))
365
{
366
case NavigationContext::NavigateResult::Success:
367
result = {std::nullopt, true};
368
break;
369
case NavigationContext::NavigateResult::NotFound:
370
result = {std::nullopt, false};
371
break;
372
case NavigationContext::NavigateResult::Ambiguous:
373
result = {"@" + aliasUnprefixed + " is not a valid alias (ambiguous)", false};
374
break;
375
}
376
return result;
377
}
378
379
Error Navigator::toAliasFallback(const std::string& aliasUnprefixed)
380
{
381
NavigationContext::NavigateResult result = navigationContext.toAliasFallback(aliasUnprefixed);
382
if (result == NavigationContext::NavigateResult::Success)
383
return std::nullopt;
384
385
std::string errorMessage = "@" + aliasUnprefixed + " is not a valid alias";
386
if (result == NavigationContext::NavigateResult::Ambiguous)
387
errorMessage += " (ambiguous)";
388
return errorMessage;
389
}
390
391
} // namespace Luau::Require
392
393