@tilua/compiler 8.0.0 → 9.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/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ WRITTEN_BY_TILUA: () => WRITTEN_BY_TILUA,
23
24
  bundle: () => bundle,
24
25
  compile: () => compile,
25
26
  loadLowerings: () => loadLowerings,
@@ -36,7 +37,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
36
37
  var import_node_fs3 = require("fs");
37
38
  var import_node_path2 = require("path");
38
39
  var import_parser7 = require("@tilua/parser");
39
- var import_luau_parser2 = require("luau-parser");
40
+ var import_luau_parser3 = require("luau-parser");
40
41
 
41
42
  // src/config.ts
42
43
  var import_node_path = require("path");
@@ -424,6 +425,10 @@ var SharedRuntime = class {
424
425
  return { declare, define };
425
426
  }
426
427
  };
428
+ function moduleArguments(context, name) {
429
+ const loader2 = context.loader?.(name);
430
+ return loader2 ? [string(name), loader2] : [string(name)];
431
+ }
427
432
  function lower(program2, scopes, options = {}) {
428
433
  const result2 = new Lowerer(program2, scopes, options).run();
429
434
  dropUnreachable(result2.statements);
@@ -740,6 +745,7 @@ var Lowerer = class {
740
745
  const plugin3 = lowerings[i].plugin;
741
746
  if (!plugin3.methodCall) continue;
742
747
  const answer = plugin3.methodCall({
748
+ options: lowerings[i].options ?? {},
743
749
  method,
744
750
  receiver,
745
751
  receiverGlobal,
@@ -771,6 +777,7 @@ var Lowerer = class {
771
777
  const plugin3 = lowerings[i].plugin;
772
778
  if (!plugin3.globalCall) continue;
773
779
  const answer = plugin3.globalCall({
780
+ options: lowerings[i].options ?? {},
774
781
  name,
775
782
  at: this.callSite(node.callee),
776
783
  get arguments() {
@@ -790,6 +797,7 @@ var Lowerer = class {
790
797
  if (name === void 0) return void 0;
791
798
  for (let i = lowerings.length - 1; i >= 0; i--) {
792
799
  const answer = lowerings[i].plugin.globalValue?.({
800
+ options: lowerings[i].options ?? {},
793
801
  name,
794
802
  at: this.callSite(node),
795
803
  use: (runtime2) => this.loweringRuntime(i, runtime2)
@@ -1043,12 +1051,12 @@ end`).body.statements, out);
1043
1051
  local2 = this.names.fresh(moduleName(key));
1044
1052
  moduleLocals.set(key, local2);
1045
1053
  locals.push(local2);
1046
- requires.push(assign([identifier(local2)], [call(context.require, [string(key)])]));
1054
+ requires.push(assign([identifier(local2)], [call(context.require, moduleArguments(context, key))]));
1047
1055
  }
1048
1056
  return local2;
1049
1057
  };
1050
1058
  const load = (key) => {
1051
- if (!moduleLocals.has(key)) requires.push(callStatement(call(context.require, [string(key)])));
1059
+ if (!moduleLocals.has(key)) requires.push(callStatement(call(context.require, moduleArguments(context, key))));
1052
1060
  };
1053
1061
  const exportBinding = (node, exported) => {
1054
1062
  const binding = node && this.bindingByDeclaration.get(node);
@@ -1157,20 +1165,22 @@ end`).body.statements, out);
1157
1165
  continue;
1158
1166
  }
1159
1167
  if (declaration.type === "ClassDeclaration") {
1160
- body.push(...this.classDeclaration(declaration, "assign"));
1168
+ body.push(...this.classDeclaration(declaration, "assign").map((s) => this.from(s, declaration)));
1161
1169
  continue;
1162
1170
  }
1163
1171
  switch (declaration.type) {
1172
+ // Stamped with where they were written, as any statement is:
1173
+ // an error in a top-level `const` names its line.
1164
1174
  case "VariableDeclaration":
1165
- body.push(...this.variableDeclaration(declaration, "assign"));
1175
+ body.push(...this.variableDeclaration(declaration, "assign").map((s) => this.from(s, declaration)));
1166
1176
  break;
1167
1177
  case "ExportDefaultStatement": {
1168
1178
  const exported = declaration.declaration;
1169
1179
  if (exported.type === "ClassDeclaration") {
1170
- body.push(...this.classDeclaration(exported, "assign"));
1171
- body.push(assign([member(exportsTable(), "default")], [this.reference(exported.name)]));
1180
+ body.push(...this.classDeclaration(exported, "assign").map((s) => this.from(s, exported)));
1181
+ body.push(this.from(assign([member(exportsTable(), "default")], [this.reference(exported.name)]), declaration));
1172
1182
  } else {
1173
- body.push(assign([member(exportsTable(), "default")], [this.expression(exported)]));
1183
+ body.push(this.from(assign([member(exportsTable(), "default")], [this.expression(exported)]), declaration));
1174
1184
  }
1175
1185
  break;
1176
1186
  }
@@ -1208,7 +1218,7 @@ end`).body.statements, out);
1208
1218
  this.report(source, `Cannot find module '${source.value}'`);
1209
1219
  return nil();
1210
1220
  }
1211
- return call(context.import ?? context.require, [string(key)]);
1221
+ return call(context.import ?? context.require, moduleArguments(context, key));
1212
1222
  }
1213
1223
  isRewritten(node) {
1214
1224
  const binding = this.bindingByDeclaration.get(node);
@@ -1407,9 +1417,9 @@ end`).body.statements, out);
1407
1417
  return this.assignment(node);
1408
1418
  case "CompoundAssignmentStatement": {
1409
1419
  if (node.operator === "??=") return this.nullishAssignment(node);
1410
- const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
1411
- if (join === "dynamic") return this.dynamicAddition(node);
1412
- const operator = join === "concat" ? "..=" : node.operator;
1420
+ const join2 = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
1421
+ if (join2 === "dynamic") return this.dynamicAddition(node);
1422
+ const operator = join2 === "concat" ? "..=" : node.operator;
1413
1423
  if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
1414
1424
  return [{
1415
1425
  type: "CompoundAssignmentStatement",
@@ -2095,9 +2105,9 @@ end
2095
2105
  return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
2096
2106
  }
2097
2107
  if (node.operator === "+") {
2098
- const join = this.options.types?.joins.get(node);
2099
- if (join === "concat") return binary("..", left, right);
2100
- if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
2108
+ const join2 = this.options.types?.joins.get(node);
2109
+ if (join2 === "concat") return binary("..", left, right);
2110
+ if (join2 === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
2101
2111
  }
2102
2112
  if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
2103
2113
  return binary(node.operator, left, right);
@@ -3064,23 +3074,29 @@ function ownExport(f, name) {
3064
3074
  // src/linemap.ts
3065
3075
  var import_parser6 = require("@tilua/parser");
3066
3076
  function statementsOf(node) {
3067
- const out = [];
3068
- const visit = (value) => {
3077
+ return statementTree(node).statements;
3078
+ }
3079
+ function statementTree(node) {
3080
+ const statements = [];
3081
+ const parents = [];
3082
+ const visit = (value, parent) => {
3069
3083
  if (!value || typeof value !== "object") return;
3070
- if (Array.isArray(value)) return void value.forEach(visit);
3084
+ if (Array.isArray(value)) return void value.forEach((item) => visit(item, parent));
3071
3085
  const record = value;
3086
+ let inside = parent;
3072
3087
  if (typeof record.type === "string" && record.type.endsWith("Statement")) {
3073
- out.push(value);
3088
+ inside = statements.push(value) - 1;
3089
+ parents.push(parent);
3074
3090
  }
3075
3091
  for (const [key, child] of Object.entries(record)) {
3076
- if (key !== "line" && key !== "column") visit(child);
3092
+ if (key !== "line" && key !== "column") visit(child, inside);
3077
3093
  }
3078
3094
  };
3079
- visit(node);
3080
- return out;
3095
+ visit(node, -1);
3096
+ return { statements, parents };
3081
3097
  }
3082
3098
  function buildLineMap(emitted, printed, origins) {
3083
- const before = statementsOf(emitted);
3099
+ const { statements: before, parents } = statementTree(emitted);
3084
3100
  const after = statementsOf(printed);
3085
3101
  if (before.length !== after.length) return /* @__PURE__ */ new Map();
3086
3102
  const map = /* @__PURE__ */ new Map();
@@ -3091,12 +3107,75 @@ function buildLineMap(emitted, printed, origins) {
3091
3107
  const line = after[i].line.start;
3092
3108
  if (!map.has(line)) map.set(line, origin);
3093
3109
  }
3110
+ const holds = /* @__PURE__ */ new Set();
3111
+ for (let i = 0; i < before.length; i++) {
3112
+ if (!origins.has(before[i])) continue;
3113
+ for (let up = parents[i]; up >= 0 && !holds.has(up); up = parents[up]) holds.add(up);
3114
+ }
3115
+ for (let i = 0; i < before.length; i++) {
3116
+ const origin = origins.get(before[i]);
3117
+ if (!origin || holds.has(i)) continue;
3118
+ for (let line = after[i].line.start + 1; line <= after[i].line.end; line++) {
3119
+ if (!map.has(line)) map.set(line, origin);
3120
+ }
3121
+ }
3094
3122
  return map;
3095
3123
  }
3096
- function lineMapSource(map) {
3124
+ var VLQ_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3125
+ function vlq(n) {
3126
+ let value = n < 0 ? -n * 2 + 1 : n * 2;
3127
+ let out = "";
3128
+ do {
3129
+ const digit = value % 32;
3130
+ value = Math.floor(value / 32);
3131
+ out += VLQ_DIGITS[value > 0 ? digit + 32 : digit];
3132
+ } while (value > 0);
3133
+ return out;
3134
+ }
3135
+ function lineMapSource(map, unpack) {
3097
3136
  const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
3098
- const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
3099
- return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
3137
+ const files = [];
3138
+ const indexes = /* @__PURE__ */ new Map();
3139
+ let data = "";
3140
+ let line = 0, file = 0, source = 0;
3141
+ for (const [at, origin] of entries) {
3142
+ const name = origin.file.replace(/\\/g, "/");
3143
+ if (!indexes.has(name)) indexes.set(name, files.push(name));
3144
+ const index2 = indexes.get(name);
3145
+ data += vlq(at - line) + vlq(index2 - file) + vlq(origin.line - source);
3146
+ line = at;
3147
+ file = index2;
3148
+ source = origin.line;
3149
+ }
3150
+ return `${unpack}({${files.map(import_parser6.luauString).join(",")}},${(0, import_parser6.luauString)(data)})`;
3151
+ }
3152
+
3153
+ // src/output.ts
3154
+ var import_luau_parser2 = require("luau-parser");
3155
+ var WRITTEN = /* @__PURE__ */ new Set([
3156
+ "compoundAssignment",
3157
+ "continue",
3158
+ "ifExpression",
3159
+ "floorDivision",
3160
+ "attributes",
3161
+ "numberLiterals"
3162
+ ]);
3163
+ function printOptions(syntax, format = {}) {
3164
+ return {
3165
+ syntax: [...syntax].some((s) => WRITTEN.has(s)) ? "luau" : "lua51",
3166
+ compact: format.minify ?? false,
3167
+ ...format.indent !== void 0 && { indent: format.indent }
3168
+ };
3169
+ }
3170
+ function printOutput(node, options) {
3171
+ try {
3172
+ return { code: (0, import_luau_parser2.print)(node, options) };
3173
+ } catch (error) {
3174
+ if (!(error instanceof import_luau_parser2.PrintError)) throw error;
3175
+ return {
3176
+ problem: `The output cannot be written as Lua 5.1: ${error.message.replace(/ \(\d+:\d+\)$/, "")}. Nothing in 'types' says the project's Lua has it \u2014 "luau" does, and "roblox" brings "luau"`
3177
+ };
3178
+ }
3100
3179
  }
3101
3180
 
3102
3181
  // src/lowering.ts
@@ -3115,7 +3194,7 @@ async function loadLowerings(modules) {
3115
3194
  });
3116
3195
  continue;
3117
3196
  }
3118
- lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
3197
+ lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library, options: module2.options });
3119
3198
  } catch (error) {
3120
3199
  problems.push({
3121
3200
  file: module2.file,
@@ -3127,17 +3206,30 @@ async function loadLowerings(modules) {
3127
3206
  }
3128
3207
 
3129
3208
  // src/bundle.ts
3209
+ var WRITTEN_BY_TILUA = "-- Written by tilua";
3130
3210
  async function bundle(options) {
3131
- const entry = (0, import_node_path2.resolve)(options.entry);
3132
3211
  const diagnostics = [];
3212
+ const from = options.entry !== void 0 ? (0, import_node_path2.resolve)(options.entry) : (0, import_node_path2.resolve)(options.root ?? process.cwd(), "tilua.config.json");
3133
3213
  const inline = options.config !== void 0 && typeof options.config !== "string";
3134
- const { config, problems } = resolveConfig(options.config, entry, options.root ?? (inline ? (0, import_node_path2.dirname)(entry) : process.cwd()));
3214
+ const { config, problems } = resolveConfig(options.config, from, options.root ?? (inline ? (0, import_node_path2.dirname)(from) : process.cwd()));
3135
3215
  for (const p of problems) diagnostics.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
3136
- const root = (0, import_node_path2.resolve)(options.root ?? config?.directory ?? (0, import_node_path2.dirname)(entry));
3216
+ const root = (0, import_node_path2.resolve)(options.root ?? config?.directory ?? (0, import_node_path2.dirname)(from));
3137
3217
  const nameOf = (file) => (0, import_node_path2.relative)(root, file).replace(/\\/g, "/").replace(/\.tilua$/, "");
3218
+ const mode = options.output ?? config?.output ?? "single";
3219
+ const perFile = mode === "files";
3220
+ const rootDir = (0, import_node_path2.resolve)(config?.rootDir ?? root);
3221
+ const entry = options.entry !== void 0 ? (0, import_node_path2.resolve)(options.entry) : void 0;
3222
+ const anchor = entry ?? config?.path ?? from;
3223
+ const stop = (message, file = anchor) => ({
3224
+ files: [],
3225
+ modules: [],
3226
+ diagnostics: [...diagnostics, { file, message, line: 1, column: 1, category: "config" }]
3227
+ });
3228
+ if (!perFile && entry === void 0) return stop('No entry to build: only "output": "files" builds without one');
3138
3229
  const sources = /* @__PURE__ */ new Map();
3139
3230
  const directives = /* @__PURE__ */ new Map();
3140
- const queue = [entry];
3231
+ const queue = perFile ? tiluaFiles(rootDir) : [entry];
3232
+ if (perFile && !queue.length) return stop(`No .tilua file under ${(0, import_node_path2.relative)(process.cwd(), rootDir) || "."}`);
3141
3233
  while (queue.length) {
3142
3234
  const file = queue.shift();
3143
3235
  if (sources.has(file)) continue;
@@ -3155,6 +3247,22 @@ async function bundle(options) {
3155
3247
  if (target && !target.endsWith(".d.tilua")) queue.push(target);
3156
3248
  }
3157
3249
  }
3250
+ if (perFile) {
3251
+ const outside = [...sources.keys()].filter((file) => isOutside(rootDir, file));
3252
+ if (outside.length) {
3253
+ return {
3254
+ files: [],
3255
+ modules: [],
3256
+ diagnostics: [...diagnostics, ...outside.map((file) => ({
3257
+ file,
3258
+ message: `This file is imported, but it is outside rootDir (${(0, import_node_path2.relative)(root, rootDir) || "."}), where every file of "files" output has to be`,
3259
+ line: 1,
3260
+ column: 1,
3261
+ category: "module"
3262
+ }))]
3263
+ };
3264
+ }
3265
+ }
3158
3266
  const analysis = analyzeModules([...sources.values()], config);
3159
3267
  const { lowerings, problems: loweringProblems } = await loadLowerings(analysis.lowerings);
3160
3268
  for (const p of loweringProblems) {
@@ -3165,18 +3273,56 @@ async function bundle(options) {
3165
3273
  const G = names.fresh("G");
3166
3274
  const requireExpression = member(identifier(G), "require");
3167
3275
  const importExpression = member(identifier(G), "import");
3168
- const mode = options.output ?? config?.output ?? "single";
3169
3276
  const byPath = syntaxOf(lowerings).has("requireByPath");
3277
+ if (perFile && !byPath) {
3278
+ return stop(`"output": "files" needs a Lua that requires a file by its path, which Lua 5.1 does not \u2014 add "luau" to 'types' ("roblox" loads it)`, config?.path ?? anchor);
3279
+ }
3280
+ const outputPath = (file) => (0, import_node_path2.relative)(rootDir, file).replace(/\\/g, "/").replace(/\.tilua$/, ".luau");
3281
+ const fileOfModule = new Map([...sources.values()].map((s) => [s.name, s.file]));
3282
+ const outDir = (0, import_node_path2.resolve)(options.outDir ?? config?.outDir ?? rootDir);
3283
+ const written = (file) => (0, import_node_path2.join)(outDir, outputPath(file));
3284
+ let host;
3285
+ let runtimeFile = (0, import_node_path2.join)(outDir, "_tilua.luau");
3286
+ if (perFile) {
3287
+ const outputs = [...sources.keys()].map(written);
3288
+ const taken = new Set(outputs.map(pathKey));
3289
+ for (let n = 2; taken.has(pathKey(runtimeFile)); n++) runtimeFile = (0, import_node_path2.join)(outDir, `_tilua${n}.luau`);
3290
+ for (let i = lowerings.length - 1; i >= 0 && !host; i--) {
3291
+ const { plugin: plugin3, options: own } = lowerings[i];
3292
+ host = plugin3.files?.({ options: own ?? {}, directory: config?.directory ?? root, outDir, files: outputs, runtime: runtimeFile });
3293
+ }
3294
+ for (const problem of host?.problems ?? []) {
3295
+ diagnostics.push({
3296
+ file: problem.file ?? config?.path ?? anchor,
3297
+ message: problem.message,
3298
+ line: 1,
3299
+ column: 1,
3300
+ category: "config",
3301
+ ...problem.severity === "warning" && { severity: "warning" }
3302
+ });
3303
+ }
3304
+ if (host?.runtime !== void 0) {
3305
+ if (isOutside(outDir, (0, import_node_path2.resolve)(host.runtime))) return stop(`The runtime has to go inside outDir, not at ${host.runtime}`);
3306
+ if (taken.has(pathKey(host.runtime))) return stop(`The runtime cannot go at ${host.runtime}: a module's file is there`);
3307
+ runtimeFile = (0, import_node_path2.resolve)(host.runtime);
3308
+ }
3309
+ }
3310
+ const placeOf = (file) => host?.place?.(file) ?? placeInFolder((0, import_node_path2.relative)(outDir, file));
3311
+ const requireFrom = (from2, to) => pathBetween(placeOf(from2), placeOf(to), host?.root);
3312
+ const isScript = (file) => host?.script?.(file) ?? false;
3170
3313
  const modules = /* @__PURE__ */ new Map();
3171
3314
  const origins = /* @__PURE__ */ new WeakMap();
3172
3315
  const shared = new SharedRuntime(names);
3173
3316
  const shaking = options.treeShaking ?? config?.treeShaking ?? false;
3317
+ const format = { indent: options.indent ?? config?.indent, minify: options.minify ?? config?.minify ?? false };
3318
+ const printing = printOptions(syntaxOf(lowerings), format);
3319
+ const mapping = !format.minify && (options.lineMap ?? config?.lineMap ?? true);
3174
3320
  const resolveIn = (file) => (specifier) => {
3175
3321
  const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
3176
3322
  return found !== void 0 && sources.has(found) ? found : void 0;
3177
3323
  };
3178
- const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
3179
- const pending = [entry];
3324
+ const shaken = shaking && entry !== void 0 && !perFile ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
3325
+ const pending = perFile ? [...sources.keys()] : [entry];
3180
3326
  while (pending.length) {
3181
3327
  const file = pending.shift();
3182
3328
  if (modules.has(file)) continue;
@@ -3209,9 +3355,16 @@ async function bundle(options) {
3209
3355
  import: importExpression,
3210
3356
  // One file has one line map. Several have one each, and a
3211
3357
  // module reads the one of the file it is in.
3212
- lines: mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
3213
- fail: `${G}.fail`,
3358
+ lines: !mapping ? void 0 : mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
3359
+ fail: mapping ? `${G}.fail` : void 0,
3214
3360
  arrays: `${G}.arrays`,
3361
+ // The file a module is in, required from this one by its
3362
+ // path, written out.
3363
+ loader: !perFile ? void 0 : (name) => {
3364
+ const target = fileOfModule.get(name);
3365
+ if (target === void 0) return void 0;
3366
+ return (0, import_luau_parser3.parseExpressionFromSource)(`function() return require(${(0, import_parser7.luauString)(requireFrom(written(file), written(target)))}) end`);
3367
+ },
3215
3368
  resolve: (specifier, dynamic) => {
3216
3369
  const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
3217
3370
  if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
@@ -3263,6 +3416,102 @@ async function bundle(options) {
3263
3416
  return { files: [], modules: moduleNames, diagnostics };
3264
3417
  }
3265
3418
  if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
3419
+ const unwritable = (problem) => ({
3420
+ files: [],
3421
+ modules: moduleNames,
3422
+ diagnostics: [...diagnostics, { file: anchor, message: problem, line: 1, column: 1, category: "config" }]
3423
+ });
3424
+ const write = (node) => {
3425
+ const printed2 = printOutput(node, printing);
3426
+ return "problem" in printed2 ? printed2 : printed2.code;
3427
+ };
3428
+ if (perFile) {
3429
+ const imported = [];
3430
+ for (const [file, source] of sources) {
3431
+ for (const { specifier, line, column } of importSites(source.program)) {
3432
+ const target = resolveIn(file)(specifier);
3433
+ if (target === void 0 || !isScript(written(target))) continue;
3434
+ imported.push({
3435
+ file,
3436
+ line,
3437
+ column,
3438
+ category: "module",
3439
+ message: `'${specifier}' is a script the game runs, which nothing can import: move what it shares into a module of its own`
3440
+ });
3441
+ }
3442
+ }
3443
+ if (imported.length) return { files: [], modules: moduleNames, diagnostics: [...diagnostics, ...imported] };
3444
+ const runtimePath = (0, import_node_path2.relative)(outDir, runtimeFile).replace(/\\/g, "/");
3445
+ const files2 = [];
3446
+ for (const [file, module2] of modules) {
3447
+ const path = outputPath(file);
3448
+ const at = written(file);
3449
+ const script = isScript(at);
3450
+ const prelude = host?.prelude?.(at);
3451
+ const body = [
3452
+ ...prelude !== void 0 ? (0, import_luau_parser3.parse)(prelude).body.statements : [],
3453
+ ...(0, import_luau_parser3.parse)(`local ${G} = require(${(0, import_parser7.luauString)(requireFrom(at, runtimeFile))})`).body.statements
3454
+ ];
3455
+ for (let i = 0; i < module2.shared.length; i += 100) {
3456
+ const some = module2.shared.slice(i, i + 100);
3457
+ body.push(local(some, some.map((name) => member(member(identifier(G), "shared"), name))));
3458
+ }
3459
+ if (module2.usesScriptArgs) body.push(...(0, import_luau_parser3.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
3460
+ body.push(assign(
3461
+ [index(member(identifier(G), "modules"), string(module2.name))],
3462
+ [moduleTable(module2)]
3463
+ ));
3464
+ const value = `${G}.${mapping ? "run" : "require"}(${(0, import_parser7.luauString)(module2.name)})`;
3465
+ const returned2 = module2.info.exports && !script ? entryReturn(lowerings, module2.name, value, () => analysis.exportsOf(file)) : { statements: [], value };
3466
+ if ("problem" in returned2) return stop(returned2.problem.message, file);
3467
+ const ending = (0, import_luau_parser3.parse)(`${returned2.statements.join("\n")}
3468
+ return ${returned2.value}`).body.statements;
3469
+ const chunk = program(body);
3470
+ if (mapping) {
3471
+ const text = write(chunk);
3472
+ if (typeof text !== "string") return unwritable(text.problem);
3473
+ const lines = buildLineMap(chunk, (0, import_luau_parser3.parse)(text), origins);
3474
+ const named = host?.place?.(at) ? `${placeOf(at).join("/")}.luau` : path;
3475
+ body.push(...mapRegistration(G, { path: named, modules: [module2] }, lineMapSource(lines, `${G}.lineMap`)));
3476
+ }
3477
+ body.push(...ending);
3478
+ const code = write(chunk);
3479
+ if (typeof code !== "string") return unwritable(code.problem);
3480
+ const from2 = (0, import_node_path2.relative)((0, import_node_path2.dirname)(at), file).replace(/\\/g, "/");
3481
+ files2.push({ path, code: `${code}
3482
+ ${WRITTEN_BY_TILUA} from ${from2}: edit that file, not this one.
3483
+ `, modules: [module2.name] });
3484
+ }
3485
+ const runtimeProgram = (0, import_luau_parser3.parse)(runtime(G, {
3486
+ several: true,
3487
+ perFile: true,
3488
+ byPath,
3489
+ files: /* @__PURE__ */ new Map(),
3490
+ imports: lowered.some((m) => m.dynamicImports.size > 0),
3491
+ mapping
3492
+ }));
3493
+ const helpers2 = shared.chunk(new Set(lowered.flatMap((m) => m.shared)));
3494
+ const helperNames = helpers2.declare.flatMap((statement) => statement.type === "LocalStatement" ? statement.names.map((n) => n.name) : []);
3495
+ runtimeProgram.body.statements.unshift(...helpers2.declare);
3496
+ runtimeProgram.body.statements.push(...helpers2.define);
3497
+ if (helperNames.length) {
3498
+ runtimeProgram.body.statements.push(assign(
3499
+ [member(identifier(G), "shared")],
3500
+ [table(helperNames.map((name) => field(name, identifier(name))))]
3501
+ ));
3502
+ }
3503
+ if (lowered.some((m) => m.usesScriptArgs)) {
3504
+ runtimeProgram.body.statements.push(...(0, import_luau_parser3.parse)(`${G}.scriptArgs = {}
3505
+ ${G}.arrays[${G}.scriptArgs] = true`).body.statements);
3506
+ }
3507
+ runtimeProgram.body.statements.push(returns([identifier(G)]));
3508
+ const runtimeCode = write(runtimeProgram);
3509
+ if (typeof runtimeCode !== "string") return unwritable(runtimeCode.problem);
3510
+ files2.push({ path: runtimePath, code: `${runtimeCode}
3511
+ ${WRITTEN_BY_TILUA}: the runtime every file of the project requires.
3512
+ `, modules: [] });
3513
+ return { code: files2[0].code, files: files2, modules: moduleNames, diagnostics };
3514
+ }
3266
3515
  const several = mode !== "single";
3267
3516
  const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
3268
3517
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
@@ -3270,17 +3519,18 @@ async function bundle(options) {
3270
3519
  for (const file of layout.slice(1)) {
3271
3520
  for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
3272
3521
  }
3273
- const program2 = (0, import_luau_parser2.parse)(runtime(G, {
3522
+ const program2 = (0, import_luau_parser3.parse)(runtime(G, {
3274
3523
  several,
3275
3524
  byPath,
3276
3525
  files: elsewhere,
3277
- imports: lowered.some((m) => m.dynamicImports.size > 0)
3526
+ imports: lowered.some((m) => m.dynamicImports.size > 0),
3527
+ mapping
3278
3528
  }));
3279
3529
  const assignment = program2.body.statements[1];
3280
3530
  if (usesScriptArgs) {
3281
3531
  program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
3282
- program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
3283
- if (several) program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
3532
+ program2.body.statements.push(...(0, import_luau_parser3.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
3533
+ if (several) program2.body.statements.push(...(0, import_luau_parser3.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
3284
3534
  }
3285
3535
  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;
3286
3536
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
@@ -3291,31 +3541,42 @@ async function bundle(options) {
3291
3541
  program2.body.statements.unshift(...helpers.declare);
3292
3542
  const ok = names.fresh("ok");
3293
3543
  const result2 = names.fresh("result");
3294
- const entryStatements = (0, import_luau_parser2.parse)(`
3544
+ const entryName = (0, import_parser7.luauString)(sources.get(entry).name);
3545
+ const returnsExports = modules.get(entry).info.exports;
3546
+ const returned = returnsExports ? entryReturn(lowerings, sources.get(entry).name, mapping ? result2 : `${G}.require(${entryName})`, () => analysis.exportsOf(entry)) : void 0;
3547
+ if (returned && "problem" in returned) return stop(returned.problem.message);
3548
+ const declarations = returned?.statements.join("\n") ?? "";
3549
+ const entryStatements = (0, import_luau_parser3.parse)(!mapping ? `${declarations}
3550
+ ${returned ? `return ${returned.value}` : `${G}.require(${entryName})`}` : `
3295
3551
  local ${ok}, ${result2} = xpcall(function()
3296
- return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry).name)})
3552
+ return ${G}.require(${entryName})
3297
3553
  end, ${G}.fail)
3298
3554
  if not ${ok} then
3299
3555
  error(${result2}, 0)
3300
3556
  end
3301
- ${modules.get(entry).info.exports ? `return ${result2}` : ""}
3557
+ ${declarations}
3558
+ ${returned ? `return ${returned.value}` : ""}
3302
3559
  `).body.statements;
3303
3560
  program2.body.statements.push(...entryStatements);
3304
- const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
3305
- if (map.size || several) {
3561
+ let printed = write(program2);
3562
+ if (typeof printed !== "string") return unwritable(printed.problem);
3563
+ const map = mapping ? buildLineMap(program2, (0, import_luau_parser3.parse)(printed), origins) : /* @__PURE__ */ new Map();
3564
+ if (mapping && (map.size || several)) {
3306
3565
  const mapStatements = [assign(
3307
3566
  [member(identifier(G), "lines")],
3308
- [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
3567
+ [(0, import_luau_parser3.parseExpressionFromSource)(lineMapSource(map, `${G}.lineMap`))]
3309
3568
  )];
3310
3569
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
3311
3570
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
3312
3571
  }
3313
3572
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
3314
- const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
3573
+ printed = write(program2);
3574
+ if (typeof printed !== "string") return unwritable(printed.problem);
3575
+ const files = [{ path: layout[0].path, code: printed + "\n", modules: layout[0].modules.map((m) => m.name) }];
3315
3576
  for (const file of layout.slice(1)) {
3316
3577
  const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3317
3578
  const body = [...fileHelpers.declare];
3318
- if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
3579
+ if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser3.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
3319
3580
  for (const module2 of file.modules) {
3320
3581
  body.push(assign(
3321
3582
  [index(member(identifier(G), "modules"), string(module2.name))],
@@ -3323,12 +3584,35 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
3323
3584
  ));
3324
3585
  }
3325
3586
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
3326
- const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
3327
- body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
3328
- files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
3587
+ if (mapping) {
3588
+ const lines = buildLineMap(chunk, (0, import_luau_parser3.parse)((0, import_luau_parser3.print)(chunk, printing)), origins);
3589
+ body.push(...mapRegistration(G, file, lineMapSource(lines, `${G}.lineMap`)));
3590
+ }
3591
+ body.push(...fileHelpers.define);
3592
+ const code = write(chunk);
3593
+ if (typeof code !== "string") return unwritable(code.problem);
3594
+ files.push({ path: file.path, code: code + "\n", modules: file.modules.map((m) => m.name) });
3329
3595
  }
3330
3596
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
3331
3597
  }
3598
+ function entryReturn(lowerings, module2, value, exports2) {
3599
+ const syntax = [...syntaxOf(lowerings)];
3600
+ for (let i = lowerings.length - 1; i >= 0; i--) {
3601
+ const { plugin: plugin3, from, options } = lowerings[i];
3602
+ if (!plugin3.entryExports) continue;
3603
+ const answer = plugin3.entryExports({ options: options ?? {}, module: module2, value, exports: exports2(), syntax });
3604
+ if (!answer) continue;
3605
+ const statements = [...answer.statements ?? []];
3606
+ try {
3607
+ (0, import_luau_parser3.parse)(`${statements.join("\n")}
3608
+ return ${answer.value}`);
3609
+ } catch (error) {
3610
+ return { problem: { message: `'${from}' answered entryExports with Luau that does not parse: ${error.message}` } };
3611
+ }
3612
+ return { statements, value: answer.value };
3613
+ }
3614
+ return { statements: [], value };
3615
+ }
3332
3616
  function moduleTable(module2) {
3333
3617
  const { names: exported, links, stars } = module2.info;
3334
3618
  const fields = [];
@@ -3348,10 +3632,6 @@ function layoutOf(mode, entry, modules, outFile) {
3348
3632
  const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry.name);
3349
3633
  const others = modules.filter((m) => m !== entry);
3350
3634
  if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry, ...others] }];
3351
- if (mode === "modules") {
3352
- const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
3353
- return [entry, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
3354
- }
3355
3635
  const byName = new Map(modules.map((m) => [m.name, m]));
3356
3636
  const reach = (root) => {
3357
3637
  const seen = /* @__PURE__ */ new Set();
@@ -3387,16 +3667,75 @@ function layoutOf(mode, entry, modules, outFile) {
3387
3667
  return files;
3388
3668
  }
3389
3669
  function loadPath(entryPath, file, byPath) {
3390
- const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
3391
- if (!byPath) return path;
3670
+ return byPath ? requirePath(entryPath, file) : import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
3671
+ }
3672
+ function placeInFolder(path) {
3673
+ const parts = path.replace(/\\/g, "/").replace(/\.luau?$/, "").split("/").filter((part) => part !== "");
3674
+ if (parts.at(-1) === "init") parts.pop();
3675
+ return parts;
3676
+ }
3677
+ function pathBetween(from, to, root) {
3678
+ if (root !== void 0 && from[0] !== to[0]) return `${root}/${to.join("/")}`;
3679
+ if (to.length > from.length && from.every((part, i) => to[i] === part)) return `@self/${to.slice(from.length).join("/")}`;
3680
+ const base = from.slice(0, -1);
3681
+ let shared = 0;
3682
+ while (shared < base.length && shared < to.length - 1 && base[shared] === to[shared]) shared++;
3683
+ const up = base.length - shared;
3684
+ return `${up === 0 ? "./" : "../".repeat(up)}${to.slice(shared).join("/")}`;
3685
+ }
3686
+ function importSites(program2) {
3687
+ const sites = [];
3688
+ for (const statement of program2.body.statements) {
3689
+ if (statement.type === "ImportStatement" || statement.type === "ExportAllStatement" || statement.type === "ExportNamedStatement" && statement.source) {
3690
+ const source = statement.source;
3691
+ sites.push({ specifier: source.value, line: source.line.start, column: source.column.start });
3692
+ }
3693
+ }
3694
+ for (const node of (0, import_parser7.importExpressions)(program2)) {
3695
+ if (node.source.type === "StringLiteral") sites.push({ specifier: node.source.value, line: node.source.line.start, column: node.source.column.start });
3696
+ }
3697
+ return sites;
3698
+ }
3699
+ function pathKey(path) {
3700
+ const full = (0, import_node_path2.resolve)(path);
3701
+ return process.platform === "win32" ? full.toLowerCase() : full;
3702
+ }
3703
+ function requirePath(from, to) {
3704
+ const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(from), to);
3392
3705
  const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
3393
3706
  return bare.startsWith("../") ? bare : `./${bare}`;
3394
3707
  }
3708
+ function tiluaFiles(folder) {
3709
+ const found = [];
3710
+ const walk2 = (at) => {
3711
+ let entries;
3712
+ try {
3713
+ entries = (0, import_node_fs3.readdirSync)(at, { withFileTypes: true });
3714
+ } catch {
3715
+ return;
3716
+ }
3717
+ for (const item of entries) {
3718
+ if (item.name.startsWith(".")) continue;
3719
+ const path = (0, import_node_path2.join)(at, item.name);
3720
+ if (item.isDirectory()) {
3721
+ if (item.name !== "node_modules") walk2(path);
3722
+ } else if (item.name.endsWith(".tilua") && !item.name.endsWith(".d.tilua")) {
3723
+ found.push(path);
3724
+ }
3725
+ }
3726
+ };
3727
+ walk2(folder);
3728
+ return found.sort();
3729
+ }
3730
+ function isOutside(folder, file) {
3731
+ const path = (0, import_node_path2.relative)(folder, file);
3732
+ return path === "" || path.startsWith("..") || (0, import_node_path2.isAbsolute)(path);
3733
+ }
3395
3734
  function mapRegistration(G, file, map) {
3396
3735
  const extension = import_node_path2.posix.extname(file.path);
3397
3736
  const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
3398
3737
  const entry = `${G}.sources[#${G}.sources + 1]`;
3399
- return (0, import_luau_parser2.parse)([
3738
+ return (0, import_luau_parser3.parse)([
3400
3739
  `${entry} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
3401
3740
  ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
3402
3741
  ].join("\n")).body.statements;
@@ -3422,7 +3761,8 @@ function loader(G, byPath) {
3422
3761
  require(file)(${G})`;
3423
3762
  }
3424
3763
  function runtime(G, options) {
3425
- const { several } = options;
3764
+ const { several, mapping } = options;
3765
+ const perFile = options.perFile ?? false;
3426
3766
  const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
3427
3767
  return `
3428
3768
  local ${G}
@@ -3430,7 +3770,7 @@ ${G} = {
3430
3770
  modules = {},
3431
3771
  -- Every array the program made, so \`typeof\` can tell one from an object.
3432
3772
  -- The keys are held weakly: being written down keeps no array alive.
3433
- arrays = setmetatable({}, { __mode = "k" }),
3773
+ arrays = setmetatable({}, { __mode = "k" }),${mapping ? `
3434
3774
  -- Filled in just before the entry runs: bundle line -> { file, line }.
3435
3775
  lines = nil,
3436
3776
  -- What an error nothing caught says, in the project's files: the bundle's
@@ -3598,32 +3938,96 @@ ${several ? `
3598
3938
  end
3599
3939
  return mapped .. "\\n" .. table.concat(frames, "\\n")
3600
3940
  end,
3601
- records = {},${several ? `
3941
+ -- A line map as it is written: the files it names, and base-64 VLQ
3942
+ -- numbers, three to a line \u2014 how far down the bundle, which file, how far
3943
+ -- down that file, each counted from the line before. It is read out the
3944
+ -- first time a line is looked up, which is when something went wrong.
3945
+ lineMap = function(files, data)
3946
+ local map = {}
3947
+ return setmetatable(map, { __index = function(_, key)
3948
+ setmetatable(map, nil)
3949
+ local digits = {}
3950
+ for i = 1, 64 do
3951
+ digits[("${VLQ_DIGITS}"):byte(i)] = i - 1
3952
+ end
3953
+ local at = 1
3954
+ local function read()
3955
+ local value, scale = 0, 1
3956
+ repeat
3957
+ local digit = digits[data:byte(at)]
3958
+ at = at + 1
3959
+ value = value + digit % 32 * scale
3960
+ scale = scale * 32
3961
+ until digit < 32
3962
+ if value % 2 == 1 then
3963
+ return -math.floor(value / 2)
3964
+ end
3965
+ return math.floor(value / 2)
3966
+ end
3967
+ local line, file, source = 0, 0, 0
3968
+ while at <= #data do
3969
+ line = line + read()
3970
+ file = file + read()
3971
+ source = source + read()
3972
+ map[line] = { files[file], source }
3973
+ end
3974
+ return map[key]
3975
+ end })
3976
+ end,` : ""}
3977
+ records = {},${several && !perFile ? `
3602
3978
  -- The file each module outside this one is in, as \`load\` is handed it.
3603
3979
  files = { ${files} },
3604
- loaded = {},
3980
+ loaded = {},` : ""}${several && mapping ? `
3605
3981
  -- Every file's line map: by the module, and by the file's name.
3606
3982
  maps = {},
3607
- sources = {},
3983
+ sources = {},` : ""}${several && !perFile ? `
3608
3984
  -- Another of the bundle's files: a function that adds its modules here.
3609
3985
  load = function(file)
3610
3986
  if ${G}.loaded[file] then
3611
3987
  return
3612
3988
  end
3613
3989
  ${G}.loaded[file] = true${loader(G, options.byPath)}
3990
+ end,` : ""}${perFile && mapping ? `
3991
+ -- What a file returns: its module's exports. The first file asked for
3992
+ -- runs under \`xpcall\`, so an error nothing caught is reported in the
3993
+ -- project's files; the files it requires in turn are inside that already.
3994
+ running = false,
3995
+ run = function(name)
3996
+ if ${G}.running then
3997
+ return ${G}.require(name)
3998
+ end
3999
+ ${G}.running = true
4000
+ local ok, result = xpcall(${G}.require, ${G}.fail, name)
4001
+ ${G}.running = false
4002
+ if not ok then
4003
+ error(result, 0)
4004
+ end
4005
+ return result
3614
4006
  end,` : ""}${options.imports ? `
3615
4007
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3616
4008
  -- rather than before its module does. \`require\` does the rest \u2014 and
3617
4009
  -- loads the file the module is in, if it is not this one.
3618
- import = function(name)
3619
- return ${G}.require(name)
4010
+ import = function(name${perFile ? ", load" : ""})
4011
+ return ${G}.require(name${perFile ? ", load" : ""})
3620
4012
  end,` : ""}
3621
- require = function(name)
4013
+ require = function(name${perFile ? ", load" : ""})
3622
4014
  local record = ${G}.records[name]
3623
4015
  if record ~= nil then
3624
4016
  return record.exports
3625
4017
  end
3626
- local module = ${G}.modules[name]${several ? `
4018
+ local module = ${G}.modules[name]${perFile ? `
4019
+ -- Its file has not run: requiring it adds the module, and runs it.
4020
+ if module == nil and load ~= nil then
4021
+ load()
4022
+ record = ${G}.records[name]
4023
+ if record ~= nil then
4024
+ return record.exports
4025
+ end
4026
+ module = ${G}.modules[name]
4027
+ end
4028
+ if module == nil then
4029
+ error(("Cannot find module '%s': nothing has loaded the file it is in"):format(name), 2)
4030
+ end` : several ? `
3627
4031
  if module == nil then
3628
4032
  ${G}.load(${G}.files[name])
3629
4033
  module = ${G}.modules[name]
@@ -3729,7 +4133,7 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
3729
4133
  function analyzeModules(modules, config) {
3730
4134
  const out = [];
3731
4135
  const analyses = /* @__PURE__ */ new Map();
3732
- const libraries = config ? (0, import_parser7.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
4136
+ const libraries = config ? (0, import_parser7.resolveTypeLibraries)(config) : { files: [], lowerings: [], options: [], problems: [] };
3733
4137
  for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
3734
4138
  const libs = libraries.files.map(parsedLibrary);
3735
4139
  const noLib = config?.noLib ?? false;
@@ -3768,7 +4172,7 @@ function analyzeModules(modules, config) {
3768
4172
  const at = d.node;
3769
4173
  said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type", severity: d.severity });
3770
4174
  }
3771
- const result2 = { types, diagnostics: said };
4175
+ const result2 = { types, diagnostics: said, scopes };
3772
4176
  if (partials === before) {
3773
4177
  analyzed.set(file, result2);
3774
4178
  const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
@@ -3825,7 +4229,16 @@ function analyzeModules(modules, config) {
3825
4229
  analyses.set(module2.file, types);
3826
4230
  out.push(...diagnostics);
3827
4231
  }
3828
- return { types: analyses, diagnostics: out, lowerings: [...libraries.lowerings] };
4232
+ const exportsOf = (file) => {
4233
+ const cached = exportsCache.get(file);
4234
+ if (cached) return cached;
4235
+ const module2 = byFile.get(file);
4236
+ const { types, scopes } = analyzeModule(file, module2.program);
4237
+ const exports2 = (0, import_parser7.moduleExports)(module2.program, scopes, types, resolverFor(file));
4238
+ exportsCache.set(file, exports2);
4239
+ return exports2;
4240
+ };
4241
+ return { types: analyses, diagnostics: out, lowerings: [...libraries.lowerings], exportsOf };
3829
4242
  }
3830
4243
  function allStatements(statements) {
3831
4244
  const out = [];
@@ -3848,7 +4261,6 @@ function allStatements(statements) {
3848
4261
  var import_node_fs4 = require("fs");
3849
4262
  var import_node_path3 = require("path");
3850
4263
  var import_parser8 = require("@tilua/parser");
3851
- var import_luau_parser3 = require("luau-parser");
3852
4264
  async function compile(source, config) {
3853
4265
  let program2;
3854
4266
  try {
@@ -3868,7 +4280,7 @@ async function compile(source, config) {
3868
4280
  line: d.node.line.start,
3869
4281
  column: d.node.column.start
3870
4282
  }));
3871
- const libraries = resolved ? (0, import_parser8.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
4283
+ const libraries = resolved ? (0, import_parser8.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], options: [], problems: [] };
3872
4284
  const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
3873
4285
  for (const p of [...problems, ...libraries.problems]) {
3874
4286
  diagnostics.push({ message: p.message, line: p.line ?? 1, column: p.column ?? 1 });
@@ -3883,10 +4295,16 @@ async function compile(source, config) {
3883
4295
  });
3884
4296
  diagnostics.push(...lowered.diagnostics);
3885
4297
  if (diagnostics.length) return { diagnostics };
3886
- return { code: (0, import_luau_parser3.print)(program(lowered.statements)) + "\n", diagnostics };
4298
+ const printed = printOutput(program(lowered.statements), printOptions(syntaxOf(lowerings), {
4299
+ indent: resolved?.indent,
4300
+ minify: resolved?.minify
4301
+ }));
4302
+ if ("problem" in printed) return { diagnostics: [{ message: printed.problem, line: 1, column: 1 }] };
4303
+ return { code: printed.code + "\n", diagnostics };
3887
4304
  }
3888
4305
  // Annotate the CommonJS export names for ESM import in node:
3889
4306
  0 && (module.exports = {
4307
+ WRITTEN_BY_TILUA,
3890
4308
  bundle,
3891
4309
  compile,
3892
4310
  loadLowerings,