@tilua/compiler 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -12,7 +12,7 @@ var import_node_path3 = require("path");
12
12
  // src/bundle.ts
13
13
  var import_node_fs2 = require("fs");
14
14
  var import_node_path2 = require("path");
15
- var import_parser3 = require("@tilua/parser");
15
+ var import_parser6 = require("@tilua/parser");
16
16
  var import_luau_parser2 = require("luau-parser");
17
17
 
18
18
  // src/config.ts
@@ -27,10 +27,11 @@ function resolveConfig(input, entry2, cwd = process.cwd()) {
27
27
  }
28
28
 
29
29
  // src/lower.ts
30
- var import_parser2 = require("@tilua/parser");
30
+ var import_parser4 = require("@tilua/parser");
31
31
  var import_luau_parser = require("luau-parser");
32
32
 
33
33
  // src/luau.ts
34
+ var import_parser2 = require("@tilua/parser");
34
35
  var NO_SPAN = { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
35
36
  var span = () => ({ line: { ...NO_SPAN.line }, column: { ...NO_SPAN.column } });
36
37
  var identifier = (name) => ({ type: "Identifier", name, ...span() });
@@ -38,7 +39,7 @@ var nil = () => ({ type: "NilLiteral", ...span() });
38
39
  var boolean = (value) => ({ type: "BooleanLiteral", value, ...span() });
39
40
  var number = (value, raw = String(value)) => ({ type: "NumberLiteral", value, raw, ...span() });
40
41
  var string = (value) => ({ type: "StringLiteral", value, raw: "", ...span() });
41
- var member = (object2, property) => isLuauName(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
42
+ var member = (object2, property) => (0, import_parser2.isLuauName)(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
42
43
  var index = (object2, key) => ({ type: "IndexExpression", object: object2, index: key, ...span() });
43
44
  var call = (callee, args2) => ({ type: "CallExpression", callee, arguments: args2, ...span() });
44
45
  var methodCall = (object2, method, args2) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args2, ...span() });
@@ -47,7 +48,7 @@ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, l
47
48
  var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
48
49
  var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
49
50
  var table = (fields) => ({ type: "TableExpression", fields, ...span() });
50
- var field = (key, value) => isLuauName(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
51
+ var field = (key, value) => (0, import_parser2.isLuauName)(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
51
52
  var block = (statements) => ({ type: "Block", statements, ...span() });
52
53
  var local = (names, init) => ({
53
54
  type: "LocalStatement",
@@ -90,32 +91,6 @@ var functionBody = (params, statements, hasVarargs = false) => ({
90
91
  ...span()
91
92
  });
92
93
  var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
93
- var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
94
- "and",
95
- "break",
96
- "do",
97
- "else",
98
- "elseif",
99
- "end",
100
- "false",
101
- "for",
102
- "function",
103
- "if",
104
- "in",
105
- "local",
106
- "nil",
107
- "not",
108
- "or",
109
- "repeat",
110
- "return",
111
- "then",
112
- "true",
113
- "until",
114
- "while"
115
- ]);
116
- function isLuauName(name) {
117
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
118
- }
119
94
 
120
95
  // src/names.ts
121
96
  var Names = class _Names {
@@ -158,6 +133,7 @@ var Names = class _Names {
158
133
 
159
134
  // src/language.ts
160
135
  var import_node_fs = require("fs");
136
+ var import_parser3 = require("@tilua/parser");
161
137
  var read = (name) => (0, import_node_fs.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
162
138
  var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\w+)/g)].map((m) => m[1]));
163
139
  var array = read("array.luau");
@@ -191,7 +167,7 @@ function objectKeys(type) {
191
167
  orders.push(keys);
192
168
  }
193
169
  const order = JSON.stringify(orders[0] ?? []);
194
- const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => JSON.stringify(key)).join(", ")} }` : "nil";
170
+ const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => (0, import_parser3.luauString)(key)).join(", ")} }` : "nil";
195
171
  return { known, open: String(open) };
196
172
  }
197
173
  var plugin = {
@@ -250,11 +226,11 @@ var Lowerer = class {
250
226
  origins = /* @__PURE__ */ new WeakMap();
251
227
  run() {
252
228
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
229
+ const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
253
230
  const helpers = this.helperDefinitions();
254
231
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
255
- const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
256
232
  return {
257
- statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
233
+ statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
258
234
  exportsName: this.exportsName,
259
235
  module: this.info,
260
236
  diagnostics: this.diagnostics,
@@ -272,7 +248,7 @@ var Lowerer = class {
272
248
  // --------------------------------------------------------
273
249
  /** A tilua name as Luau can write it: `local` is a keyword there. */
274
250
  name(name) {
275
- if (!LUAU_KEYWORDS.has(name)) return name;
251
+ if (!import_parser2.LUAU_KEYWORDS.has(name)) return name;
276
252
  let renamed = this.renamed.get(name);
277
253
  if (!renamed) {
278
254
  renamed = this.names.fresh(`${name}_`);
@@ -304,11 +280,36 @@ var Lowerer = class {
304
280
  helper(kind) {
305
281
  let name = this.helpers.get(kind);
306
282
  if (!name) {
307
- name = this.names.fresh(`tilua_${kind}`);
283
+ name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
308
284
  this.helpers.set(kind, name);
309
285
  }
310
286
  return identifier(name);
311
287
  }
288
+ // `typeof v` tells an array from an object, which Luau cannot: both are
289
+ // tables. So every array the program makes — `[a, b]`, a rest parameter,
290
+ // what `:map` answers — is written down as one, in a table that holds its
291
+ // keys weakly, and `typeof` looks there. A table nothing wrote down (one a
292
+ // Lua library made) is told by its type where that says, and by its shape
293
+ // where it does not.
294
+ /** `value`, a new array, written down as one. */
295
+ array(value) {
296
+ const marked = call(this.helper("array"), [value]);
297
+ this.marked.add(marked);
298
+ return marked;
299
+ }
300
+ /** The calls `array` made: each answers the one table it was given. */
301
+ marked = /* @__PURE__ */ new WeakSet();
302
+ /** Luau reaching the record of arrays: the bundle's, or this file's own. */
303
+ arrays() {
304
+ return this.options.module?.arrays ?? this.helper("arrays").name;
305
+ }
306
+ typeofExpression(node) {
307
+ const known = this.options.types?.typeofTables.get(node);
308
+ let argument = node.argument;
309
+ while (argument.type === "ParenthesizedExpression") argument = argument.expression;
310
+ const value = this.expression(argument);
311
+ return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
312
+ }
312
313
  /** The local a library's runtime table gets in this file, emitting it the
313
314
  * first time something asks. Keyed by library and by the key the library
314
315
  * itself chose, so two libraries' tables never collide. */
@@ -376,7 +377,7 @@ var Lowerer = class {
376
377
  const type = this.options.types?.typeOf.get(argument);
377
378
  return {
378
379
  type,
379
- typeText: type ? (0, import_parser2.formatType)(type) : void 0,
380
+ typeText: type ? (0, import_parser4.formatType)(type) : void 0,
380
381
  source: this.writtenSource(argument),
381
382
  declaration: declaration ? this.writtenSource(declaration) : void 0,
382
383
  spread: argument.type === "SpreadElement"
@@ -503,6 +504,46 @@ var Lowerer = class {
503
504
  }
504
505
  helperDefinitions() {
505
506
  const out2 = [];
507
+ const runtimes = this.runtimeDefinitions();
508
+ if (this.helpers.has("concat")) this.helper("array");
509
+ const marker = this.helpers.get("array");
510
+ const typeOf = this.helpers.get("typeof");
511
+ if (marker || typeOf) {
512
+ const arrays = this.arrays();
513
+ const own = this.helpers.get("arrays");
514
+ if (own) {
515
+ out2.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
516
+ }
517
+ if (marker) {
518
+ out2.push(...(0, import_luau_parser.parse)(`
519
+ local function ${marker}(t)
520
+ ${arrays}[t] = true
521
+ return t
522
+ end`).body.statements);
523
+ }
524
+ if (typeOf) {
525
+ out2.push(...(0, import_luau_parser.parse)(`
526
+ local function ${typeOf}(value, known)
527
+ local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
528
+ if name ~= "table" then
529
+ return name
530
+ end
531
+ if known ~= nil then
532
+ return known
533
+ end
534
+ if ${arrays}[value] then
535
+ return "array"
536
+ end
537
+ if ${this.builtin("getmetatable").name}(value) ~= nil then
538
+ return "object"
539
+ end
540
+ if #value > 0 then
541
+ return "array"
542
+ end
543
+ return "object"
544
+ end`).body.statements);
545
+ }
546
+ }
506
547
  const assign2 = this.helpers.get("assign");
507
548
  if (assign2) {
508
549
  out2.push(localFunction(assign2, functionBody(["target"], [
@@ -517,25 +558,11 @@ var Lowerer = class {
517
558
  returns([identifier("target")])
518
559
  ], true)));
519
560
  }
520
- for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
521
- const { plugin: plugin2, from } = this.lowerings[index2];
522
- const source = plugin2.runtime?.[runtime2];
523
- if (source === void 0) {
524
- this.diagnostics.push({
525
- message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
526
- line: 1,
527
- column: 1
528
- });
529
- continue;
530
- }
531
- const lines = this.options.module?.lines ?? "nil";
532
- const fail = this.options.module?.fail ?? "nil";
533
- out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
534
- }
561
+ out2.push(...runtimes);
535
562
  const concat = this.helpers.get("concat");
536
563
  if (concat) {
537
564
  out2.push(localFunction(concat, functionBody([], [
538
- local(["result"], [table([])]),
565
+ local(["result"], [this.array(table([]))]),
539
566
  numericFor("i", number(1), selectCount(this.builtin("select")), [
540
567
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
541
568
  callStatement(call(member(this.builtin("table"), "move"), [
@@ -551,6 +578,26 @@ var Lowerer = class {
551
578
  }
552
579
  return out2;
553
580
  }
581
+ /** The runtime tables the file's libraries were asked for. */
582
+ runtimeDefinitions() {
583
+ const out2 = [];
584
+ for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
585
+ const { plugin: plugin2, from } = this.lowerings[index2];
586
+ const source = plugin2.runtime?.[runtime2];
587
+ if (source === void 0) {
588
+ this.diagnostics.push({
589
+ message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
590
+ line: 1,
591
+ column: 1
592
+ });
593
+ continue;
594
+ }
595
+ const lines = this.options.module?.lines ?? "nil";
596
+ const fail = this.options.module?.fail ?? "nil";
597
+ out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
598
+ }
599
+ return out2;
600
+ }
554
601
  // --------------------------------------------------------
555
602
  // Modules
556
603
  // --------------------------------------------------------
@@ -1303,7 +1350,7 @@ end
1303
1350
  number(pattern.elements.length + 1),
1304
1351
  unary("#", source),
1305
1352
  number(1),
1306
- table([])
1353
+ this.array(table([]))
1307
1354
  ]);
1308
1355
  const rest = this.restTarget(pattern.rest, elements, mode, after);
1309
1356
  after.push(...rest.then);
@@ -1357,7 +1404,7 @@ end
1357
1404
  const names = [];
1358
1405
  for (const p of params) {
1359
1406
  if (p.rest) {
1360
- prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
1407
+ prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
1361
1408
  continue;
1362
1409
  }
1363
1410
  const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
@@ -1415,6 +1462,7 @@ end
1415
1462
  return binary(node.operator, left, right);
1416
1463
  }
1417
1464
  case "UnaryExpression":
1465
+ if (node.operator === "typeof") return this.typeofExpression(node);
1418
1466
  return unary(node.operator, this.expression(node.argument));
1419
1467
  case "SuperExpression":
1420
1468
  return this.superClassReference(node);
@@ -1455,6 +1503,9 @@ end
1455
1503
  callValues(node) {
1456
1504
  const chain = optionalChain(node);
1457
1505
  if (chain) return this.optionalChainExpression(chain);
1506
+ if (node.type === "CallExpression" && (node.callee.type === "SuperExpression" || node.callee.type === "MemberExpression" && node.callee.object.type === "SuperExpression")) {
1507
+ return this.expression(node);
1508
+ }
1458
1509
  const object2 = linkObject(node);
1459
1510
  if (node.type === "CallExpression" && object2.type === "Identifier") {
1460
1511
  return this.link(node, this.reference(object2));
@@ -1466,7 +1517,7 @@ end
1466
1517
  * kept to one there, `f((g()))`. Every value is taken only where the
1467
1518
  * program asks for them, as an array's last element: `[g()]`. */
1468
1519
  keptToOne(value) {
1469
- return expandsToMany(value) ? parenthesized(value) : value;
1520
+ return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
1470
1521
  }
1471
1522
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1472
1523
  isLanguageGlobal(node) {
@@ -1520,7 +1571,7 @@ end
1520
1571
  ...args2
1521
1572
  ]);
1522
1573
  }
1523
- if (!isLuauName(node.method.name)) {
1574
+ if (!import_parser2.isLuauName(node.method.name)) {
1524
1575
  this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
1525
1576
  }
1526
1577
  return methodCall(object2, node.method.name, args2);
@@ -1747,7 +1798,7 @@ end
1747
1798
  arrayExpression(node) {
1748
1799
  const elements = packs(node.elements).items;
1749
1800
  if (!elements.some((e) => e.type === "SpreadElement")) {
1750
- return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1801
+ return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
1751
1802
  }
1752
1803
  const parts = [];
1753
1804
  let current = [];
@@ -1882,7 +1933,7 @@ function topLevelDeclaration(statements, name) {
1882
1933
  function moduleName(key) {
1883
1934
  const last = key.split("/").filter(Boolean).pop() ?? "module";
1884
1935
  const cleaned = last.replace(/[^A-Za-z0-9_]/g, "_");
1885
- return /^[A-Za-z_]/.test(cleaned) && !LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
1936
+ return /^[A-Za-z_]/.test(cleaned) && !import_parser2.LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
1886
1937
  }
1887
1938
  function alwaysTruthy(value) {
1888
1939
  switch (value.type) {
@@ -1997,6 +2048,7 @@ var CLASS_METAMETHODS = [
1997
2048
  ];
1998
2049
 
1999
2050
  // src/linemap.ts
2051
+ var import_parser5 = require("@tilua/parser");
2000
2052
  function statementsOf(node) {
2001
2053
  const out2 = [];
2002
2054
  const visit = (value) => {
@@ -2029,7 +2081,7 @@ function buildLineMap(emitted, printed, origins) {
2029
2081
  }
2030
2082
  function lineMapSource(map) {
2031
2083
  const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
2032
- const quoted = (s) => JSON.stringify(s.replace(/\\/g, "/"));
2084
+ const quoted = (s) => (0, import_parser5.luauString)(s.replace(/\\/g, "/"));
2033
2085
  return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
2034
2086
  }
2035
2087
 
@@ -2078,13 +2130,13 @@ async function bundle(options) {
2078
2130
  const parsed = parseFile(file, diagnostics, directives);
2079
2131
  if (!parsed) return { modules: [], diagnostics };
2080
2132
  const { program: program2, text } = parsed;
2081
- const scopes = (0, import_parser3.analyzeScopes)(program2);
2133
+ const scopes = (0, import_parser6.analyzeScopes)(program2);
2082
2134
  for (const d of scopes.diagnostics) {
2083
2135
  diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
2084
2136
  }
2085
2137
  sources.set(file, { file, name: nameOf(file), text, program: program2, scopes });
2086
2138
  for (const specifier of importedSpecifiers(program2)) {
2087
- const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2139
+ const target = (0, import_parser6.resolveModulePath)(file, specifier, config2);
2088
2140
  if (target && !target.endsWith(".d.tilua")) queue.push(target);
2089
2141
  }
2090
2142
  }
@@ -2117,8 +2169,9 @@ async function bundle(options) {
2117
2169
  require: requireExpression,
2118
2170
  lines: `${G}.lines`,
2119
2171
  fail: `${G}.fail`,
2172
+ arrays: `${G}.arrays`,
2120
2173
  resolve: (specifier) => {
2121
- const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2174
+ const target = (0, import_parser6.resolveModulePath)(file, specifier, config2);
2122
2175
  if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
2123
2176
  pending.push(target);
2124
2177
  return sources.get(target).name;
@@ -2140,14 +2193,14 @@ async function bundle(options) {
2140
2193
  }
2141
2194
  for (const [file, fileDirectives] of directives) {
2142
2195
  const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
2143
- const { kept, unusedExpectErrors } = (0, import_parser3.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2196
+ const { kept, unusedExpectErrors } = (0, import_parser6.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2144
2197
  const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
2145
2198
  const remaining = diagnostics.filter((d) => !suppressed.has(d));
2146
2199
  diagnostics.length = 0;
2147
2200
  diagnostics.push(...remaining);
2148
2201
  if (options.typeCheck === false) continue;
2149
2202
  for (const d of unusedExpectErrors) {
2150
- diagnostics.push({ file, message: import_parser3.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2203
+ diagnostics.push({ file, message: import_parser6.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2151
2204
  }
2152
2205
  }
2153
2206
  const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
@@ -2162,6 +2215,7 @@ async function bundle(options) {
2162
2215
  const assignment = program.body.statements[1];
2163
2216
  if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2164
2217
  program.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2218
+ program.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
2165
2219
  }
2166
2220
  const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
2167
2221
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
@@ -2183,7 +2237,7 @@ async function bundle(options) {
2183
2237
  const result = names.fresh("result");
2184
2238
  const entryStatements = (0, import_luau_parser2.parse)(`
2185
2239
  local ${ok}, ${result} = xpcall(function()
2186
- return ${G}.require(${JSON.stringify(sources.get(entry2).name)})
2240
+ return ${G}.require(${(0, import_parser6.luauString)(sources.get(entry2).name)})
2187
2241
  end, ${G}.fail)
2188
2242
  if not ${ok} then
2189
2243
  error(${result}, 0)
@@ -2205,6 +2259,9 @@ function runtime(G) {
2205
2259
  local ${G}
2206
2260
  ${G} = {
2207
2261
  modules = {},
2262
+ -- Every array the program made, so \`typeof\` can tell one from an object.
2263
+ -- The keys are held weakly: being written down keeps no array alive.
2264
+ arrays = setmetatable({}, { __mode = "k" }),
2208
2265
  -- Filled in just before the entry runs: bundle line -> { file, line }.
2209
2266
  lines = nil,
2210
2267
  -- What an error nothing caught says, in the project's files: the bundle's
@@ -2212,19 +2269,64 @@ ${G} = {
2212
2269
  -- and a traceback of those places follows. A message that is not a string
2213
2270
  -- is left as it is, and a position that is not this bundle's (a library
2214
2271
  -- that raised its error with one already mapped) is left alone.
2272
+ --
2273
+ -- It is an \`xpcall\` handler, and a handler that fails loses the error
2274
+ -- altogether: Luau reports only "error in error handling". So the work is
2275
+ -- done under \`pcall\`, leaning on nothing it has not checked is there (an
2276
+ -- executor may lack \`debug.info\`, or refuse it), and an error it cannot
2277
+ -- map is still answered as Luau raised it, with why.
2215
2278
  fail = function(message)
2279
+ -- Taken first, before anything that can fail. Level 3 is the frame
2280
+ -- that raised: past \`pcall\` and this handler.
2281
+ local trace
2282
+ if debug ~= nil and debug.traceback ~= nil then
2283
+ local ok, text = pcall(debug.traceback, "", 3)
2284
+ if ok and type(text) == "string" then
2285
+ trace = text
2286
+ end
2287
+ end
2288
+ local ok, described = pcall(${G}.describe, message, trace)
2289
+ if ok then
2290
+ return described
2291
+ end
2292
+ if type(message) ~= "string" then
2293
+ return message
2294
+ end
2295
+ local why = type(described) == "string" and described or "unknown"
2296
+ return message .. "\\n(tilua could not map this error to the project's files: " .. why .. ")" .. (trace or "")
2297
+ end,
2298
+ -- \`fail\`'s work. \`trace\` is a traceback from where the error was
2299
+ -- raised, or nil when none could be taken.
2300
+ describe = function(message, trace)
2216
2301
  if type(message) ~= "string" then
2217
2302
  return message
2218
2303
  end
2219
2304
  local lines = ${G}.lines
2220
2305
  if lines == nil then
2221
- return debug.traceback(message, 2)
2306
+ return message .. (trace or "")
2222
2307
  end
2308
+ -- The bundle's name, as a position in a message spells it: where this
2309
+ -- function is, asked of the debug library by the function rather
2310
+ -- than by a level, which \`pcall\` would shift. Failing that, a message
2311
+ -- starts with the position it was raised at.
2223
2312
  local chunk
2224
- if debug.info ~= nil then
2225
- chunk = debug.info(1, "s")
2226
- else
2227
- chunk = debug.getinfo(1, "S").short_src
2313
+ if debug ~= nil and debug.info ~= nil then
2314
+ local found, source = pcall(debug.info, ${G}.describe, "s")
2315
+ if found and type(source) == "string" then
2316
+ chunk = source
2317
+ end
2318
+ end
2319
+ if chunk == nil and debug ~= nil and debug.getinfo ~= nil then
2320
+ local found, info = pcall(debug.getinfo, ${G}.describe, "S")
2321
+ if found and type(info) == "table" and type(info.short_src) == "string" then
2322
+ chunk = info.short_src
2323
+ end
2324
+ end
2325
+ if chunk == nil then
2326
+ chunk = message:match("^(.-):%d+:")
2327
+ end
2328
+ if chunk == nil then
2329
+ return message .. (trace or "")
2228
2330
  end
2229
2331
  local prefix = chunk:gsub("%p", "%%%0")
2230
2332
  local function place(line)
@@ -2238,7 +2340,7 @@ ${G} = {
2238
2340
  return place(line)
2239
2341
  end)
2240
2342
  local frames = {}
2241
- for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
2343
+ for frame in (trace or ""):gmatch("[^\\n]+") do
2242
2344
  local line = frame:match(prefix .. ":(%d+)")
2243
2345
  local at = line ~= nil and place(line) or nil
2244
2346
  if at ~= nil then
@@ -2323,13 +2425,13 @@ function parseFile(file, diagnostics, directives) {
2323
2425
  return void 0;
2324
2426
  }
2325
2427
  try {
2326
- const program = (0, import_parser3.parse)(text);
2327
- const found = (0, import_parser3.directivesOf)(text);
2428
+ const program = (0, import_parser6.parse)(text);
2429
+ const found = (0, import_parser6.directivesOf)(text);
2328
2430
  directives.set(file, found);
2329
2431
  if (stamp !== void 0) parsedFiles.set(file, { stamp, program, text, directives: found });
2330
2432
  return { program, text };
2331
2433
  } catch (error) {
2332
- if (error instanceof import_parser3.ParseError || error instanceof import_parser3.LexError) {
2434
+ if (error instanceof import_parser6.ParseError || error instanceof import_parser6.LexError) {
2333
2435
  const { line, column } = error;
2334
2436
  diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
2335
2437
  return void 0;
@@ -2351,7 +2453,7 @@ function parsedLibrary(file) {
2351
2453
  }
2352
2454
  const cached = libraryCache.get(file);
2353
2455
  if (cached && cached.stamp === stamp) return cached.program;
2354
- const program = (0, import_parser3.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
2456
+ const program = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
2355
2457
  libraryCache.set(file, { stamp, program });
2356
2458
  return program;
2357
2459
  }
@@ -2359,7 +2461,7 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
2359
2461
  function analyzeModules(modules, config2) {
2360
2462
  const out2 = [];
2361
2463
  const analyses = /* @__PURE__ */ new Map();
2362
- const libraries = config2 ? (0, import_parser3.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
2464
+ const libraries = config2 ? (0, import_parser6.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
2363
2465
  for (const p of libraries.problems) out2.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2364
2466
  const libs = libraries.files.map(parsedLibrary);
2365
2467
  const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
@@ -2379,14 +2481,14 @@ function analyzeModules(modules, config2) {
2379
2481
  readWhile.set(file, remembered.read);
2380
2482
  return remembered.result;
2381
2483
  }
2382
- const scopes = (0, import_parser3.analyzeScopes)(program, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2484
+ const scopes = (0, import_parser6.analyzeScopes)(program, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2383
2485
  const said = [];
2384
2486
  for (const d of scopes.diagnostics) {
2385
2487
  if (d.kind !== "undeclared") continue;
2386
2488
  said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2387
2489
  }
2388
2490
  const before = partials;
2389
- const types = (0, import_parser3.analyzeTypes)(program, scopes, {
2491
+ const types = (0, import_parser6.analyzeTypes)(program, scopes, {
2390
2492
  libs,
2391
2493
  resolveModule: resolverFor(file),
2392
2494
  reportUnknownTypes: libs.length > 0
@@ -2409,7 +2511,7 @@ function analyzeModules(modules, config2) {
2409
2511
  return result;
2410
2512
  };
2411
2513
  const resolverFor = (file) => (specifier) => {
2412
- const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2514
+ const target = (0, import_parser6.resolveModulePath)(file, specifier, config2);
2413
2515
  if (!target) return void 0;
2414
2516
  const noteRead = () => {
2415
2517
  const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
@@ -2430,16 +2532,16 @@ function analyzeModules(modules, config2) {
2430
2532
  let program = byFile.get(target)?.program;
2431
2533
  if (!program) {
2432
2534
  try {
2433
- program = (0, import_parser3.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
2535
+ program = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
2434
2536
  } catch {
2435
2537
  return void 0;
2436
2538
  }
2437
2539
  }
2438
2540
  inProgress.add(target);
2439
2541
  try {
2440
- const scopes = (0, import_parser3.analyzeScopes)(program, { builtinGlobals: globals });
2441
- const types = byFile.has(target) ? analyzeModule(target, program).types : (0, import_parser3.analyzeTypes)(program, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2442
- const exports2 = (0, import_parser3.moduleExports)(program, scopes, types, resolverFor(target));
2542
+ const scopes = (0, import_parser6.analyzeScopes)(program, { builtinGlobals: globals });
2543
+ const types = byFile.has(target) ? analyzeModule(target, program).types : (0, import_parser6.analyzeTypes)(program, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2544
+ const exports2 = (0, import_parser6.moduleExports)(program, scopes, types, resolverFor(target));
2443
2545
  exportsCache.set(target, exports2);
2444
2546
  noteRead();
2445
2547
  return exports2;
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  bundle
4
- } from "./chunk-X4DM2BX2.js";
4
+ } from "./chunk-VZ7XC4BG.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { writeFileSync } from "fs";