Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Config/src/Config.cpp
2746 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/Config.h"
3
4
#include "Luau/Lexer.h"
5
#include "Luau/StringUtils.h"
6
#include <algorithm>
7
#include <memory>
8
#include <string>
9
10
namespace Luau
11
{
12
13
using Error = std::optional<std::string>;
14
15
Config::Config()
16
{
17
enabledLint.setDefaults();
18
}
19
20
Config::Config(const Config& other)
21
: mode(other.mode)
22
, parseOptions(other.parseOptions)
23
, enabledLint(other.enabledLint)
24
, fatalLint(other.fatalLint)
25
, lintErrors(other.lintErrors)
26
, typeErrors(other.typeErrors)
27
, globals(other.globals)
28
{
29
for (const auto& [_, aliasInfo] : other.aliases)
30
{
31
setAlias(aliasInfo.originalCase, aliasInfo.value, std::string(aliasInfo.configLocation));
32
}
33
}
34
35
Config& Config::operator=(const Config& other)
36
{
37
if (this != &other)
38
{
39
Config copy(other);
40
std::swap(*this, copy);
41
}
42
return *this;
43
}
44
45
void Config::setAlias(std::string alias, std::string value, const std::string& configLocation)
46
{
47
std::string lowercasedAlias = alias;
48
std::transform(
49
lowercasedAlias.begin(),
50
lowercasedAlias.end(),
51
lowercasedAlias.begin(),
52
[](unsigned char c)
53
{
54
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
55
}
56
);
57
58
AliasInfo& info = aliases[lowercasedAlias];
59
info.value = std::move(value);
60
info.originalCase = std::move(alias);
61
62
if (!configLocationCache.contains(configLocation))
63
configLocationCache[configLocation] = std::make_unique<std::string>(configLocation);
64
65
info.configLocation = *configLocationCache[configLocation];
66
}
67
68
static Error parseBoolean(bool& result, const std::string& value)
69
{
70
if (value == "true")
71
result = true;
72
else if (value == "false")
73
result = false;
74
else
75
return Error{"Bad setting '" + value + "'. Valid options are true and false"};
76
77
return std::nullopt;
78
}
79
80
Error parseModeString(Mode& mode, const std::string& modeString, bool compat)
81
{
82
if (modeString == "nocheck")
83
mode = Mode::NoCheck;
84
else if (modeString == "strict")
85
mode = Mode::Strict;
86
else if (modeString == "nonstrict")
87
mode = Mode::Nonstrict;
88
else if (modeString == "noinfer" && compat)
89
mode = Mode::NoCheck;
90
else
91
return Error{"Bad mode \"" + modeString + "\". Valid options are nocheck, nonstrict, and strict"};
92
93
return std::nullopt;
94
}
95
96
static Error parseLintRuleStringForCode(
97
LintOptions& enabledLints,
98
LintOptions& fatalLints,
99
LintWarning::Code code,
100
const std::string& value,
101
bool compat
102
)
103
{
104
if (value == "true")
105
{
106
enabledLints.enableWarning(code);
107
}
108
else if (value == "false")
109
{
110
enabledLints.disableWarning(code);
111
}
112
else if (compat)
113
{
114
if (value == "enabled")
115
{
116
enabledLints.enableWarning(code);
117
fatalLints.disableWarning(code);
118
}
119
else if (value == "disabled")
120
{
121
enabledLints.disableWarning(code);
122
fatalLints.disableWarning(code);
123
}
124
else if (value == "fatal")
125
{
126
enabledLints.enableWarning(code);
127
fatalLints.enableWarning(code);
128
}
129
else
130
{
131
return Error{"Bad setting '" + value + "'. Valid options are enabled, disabled, and fatal"};
132
}
133
}
134
else
135
{
136
return Error{"Bad setting '" + value + "'. Valid options are true and false"};
137
}
138
139
return std::nullopt;
140
}
141
142
Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat)
143
{
144
if (warningName == "*")
145
{
146
for (int code = LintWarning::Code_Unknown; code < LintWarning::Code__Count; ++code)
147
{
148
if (auto err = parseLintRuleStringForCode(enabledLints, fatalLints, LintWarning::Code(code), value, compat))
149
return Error{"In key " + warningName + ": " + *err};
150
}
151
}
152
else
153
{
154
LintWarning::Code code = LintWarning::parseName(warningName.c_str());
155
156
if (code == LintWarning::Code_Unknown)
157
return Error{"Unknown lint " + warningName};
158
159
if (auto err = parseLintRuleStringForCode(enabledLints, fatalLints, code, value, compat))
160
return Error{"In key " + warningName + ": " + *err};
161
}
162
163
return std::nullopt;
164
}
165
166
bool isValidAlias(const std::string& alias)
167
{
168
if (alias.empty())
169
return false;
170
171
bool aliasIsNotAPath = alias != "." && alias != ".." && alias.find_first_of("\\/") == std::string::npos;
172
173
if (!aliasIsNotAPath)
174
return false;
175
176
for (size_t i = 0; i < alias.size(); i++)
177
{
178
char ch = alias[i];
179
if (i == 0 && ch == '@')
180
continue;
181
182
bool isupper = 'A' <= ch && ch <= 'Z';
183
bool islower = 'a' <= ch && ch <= 'z';
184
bool isdigit = '0' <= ch && ch <= '9';
185
if (!isupper && !islower && !isdigit && ch != '-' && ch != '_' && ch != '.')
186
return false;
187
}
188
189
return true;
190
}
191
192
Error parseAlias(
193
Config& config,
194
const std::string& aliasKey,
195
const std::string& aliasValue,
196
const std::optional<ConfigOptions::AliasOptions>& aliasOptions
197
)
198
{
199
if (!isValidAlias(aliasKey))
200
return Error{"Invalid alias " + aliasKey};
201
202
if (!aliasOptions)
203
return Error("Cannot parse aliases without alias options");
204
205
if (aliasOptions->overwriteAliases || !config.aliases.contains(aliasKey))
206
config.setAlias(aliasKey, aliasValue, aliasOptions->configLocation);
207
208
return std::nullopt;
209
}
210
211
static void next(Lexer& lexer)
212
{
213
lexer.next();
214
215
// skip C-style comments as Lexer only understands Lua-style comments atm
216
while (lexer.current().type == Luau::Lexeme::FloorDiv)
217
lexer.nextline();
218
}
219
220
static Error fail(Lexer& lexer, const char* message)
221
{
222
Lexeme cur = lexer.current();
223
224
return format("Expected %s at line %d, got %s instead", message, cur.location.begin.line + 1, cur.toString().c_str());
225
}
226
227
template<typename Action>
228
static Error parseJson(const std::string& contents, Action action)
229
{
230
Allocator allocator;
231
AstNameTable names(allocator);
232
Lexer lexer(contents.data(), contents.size(), names);
233
next(lexer);
234
235
std::vector<std::string> keys;
236
bool arrayTop = false; // we don't support nested arrays
237
238
if (lexer.current().type != '{')
239
return fail(lexer, "'{'");
240
next(lexer);
241
242
for (;;)
243
{
244
if (arrayTop)
245
{
246
if (lexer.current().type == ']')
247
{
248
next(lexer);
249
arrayTop = false;
250
251
LUAU_ASSERT(!keys.empty());
252
keys.pop_back();
253
254
if (lexer.current().type == ',')
255
next(lexer);
256
else if (lexer.current().type != '}')
257
return fail(lexer, "',' or '}'");
258
}
259
else if (lexer.current().type == Lexeme::QuotedString)
260
{
261
std::string value(lexer.current().data, lexer.current().getLength());
262
next(lexer);
263
264
if (Error err = action(keys, value))
265
return err;
266
267
if (lexer.current().type == ',')
268
next(lexer);
269
else if (lexer.current().type != ']')
270
return fail(lexer, "',' or ']'");
271
}
272
else
273
return fail(lexer, "array element or ']'");
274
}
275
else
276
{
277
if (lexer.current().type == '}')
278
{
279
next(lexer);
280
281
if (keys.empty())
282
{
283
if (lexer.current().type != Lexeme::Eof)
284
return fail(lexer, "end of file");
285
286
return {};
287
}
288
else
289
keys.pop_back();
290
291
if (lexer.current().type == ',')
292
next(lexer);
293
else if (lexer.current().type != '}')
294
return fail(lexer, "',' or '}'");
295
}
296
else if (lexer.current().type == Lexeme::QuotedString)
297
{
298
std::string key(lexer.current().data, lexer.current().getLength());
299
next(lexer);
300
301
keys.push_back(key);
302
303
if (lexer.current().type != ':')
304
return fail(lexer, "':'");
305
next(lexer);
306
307
if (lexer.current().type == '{' || lexer.current().type == '[')
308
{
309
arrayTop = (lexer.current().type == '[');
310
next(lexer);
311
}
312
else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue ||
313
lexer.current().type == Lexeme::ReservedFalse)
314
{
315
std::string value = lexer.current().type == Lexeme::QuotedString
316
? std::string(lexer.current().data, lexer.current().getLength())
317
: (lexer.current().type == Lexeme::ReservedTrue ? "true" : "false");
318
next(lexer);
319
320
if (Error err = action(keys, value))
321
return err;
322
323
keys.pop_back();
324
325
if (lexer.current().type == ',')
326
next(lexer);
327
else if (lexer.current().type != '}')
328
return fail(lexer, "',' or '}'");
329
}
330
else
331
return fail(lexer, "field value");
332
}
333
else
334
return fail(lexer, "field key");
335
}
336
}
337
338
return {};
339
}
340
341
Error parseConfig(const std::string& contents, Config& config, const ConfigOptions& options)
342
{
343
return parseJson(
344
contents,
345
[&](const std::vector<std::string>& keys, const std::string& value) -> Error
346
{
347
if (keys.size() == 1 && keys[0] == "languageMode")
348
return parseModeString(config.mode, value, options.compat);
349
else if (keys.size() == 2 && keys[0] == "lint")
350
return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, options.compat);
351
else if (keys.size() == 1 && keys[0] == "lintErrors")
352
return parseBoolean(config.lintErrors, value);
353
else if (keys.size() == 1 && keys[0] == "typeErrors")
354
return parseBoolean(config.typeErrors, value);
355
else if (keys.size() == 1 && keys[0] == "globals")
356
{
357
config.globals.push_back(value);
358
return std::nullopt;
359
}
360
else if (keys.size() == 2 && keys[0] == "aliases")
361
return parseAlias(config, keys[1], value, options.aliasOptions);
362
else if (options.compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")
363
return parseModeString(config.mode, value, options.compat);
364
else
365
{
366
std::vector<std::string_view> keysv(keys.begin(), keys.end());
367
return "Unknown key " + join(keysv, "/");
368
}
369
}
370
);
371
}
372
373
} // namespace Luau
374
375