@tilua/compiler 8.1.0 → 9.0.1

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,6 +3107,18 @@ 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
3124
  var VLQ_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -3122,6 +3150,34 @@ function lineMapSource(map, unpack) {
3122
3150
  return `${unpack}({${files.map(import_parser6.luauString).join(",")}},${(0, import_parser6.luauString)(data)})`;
3123
3151
  }
3124
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
+ }
3179
+ }
3180
+
3125
3181
  // src/lowering.ts
3126
3182
  var import_node_url = require("url");
3127
3183
  async function loadLowerings(modules) {
@@ -3138,7 +3194,7 @@ async function loadLowerings(modules) {
3138
3194
  });
3139
3195
  continue;
3140
3196
  }
3141
- lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
3197
+ lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library, options: module2.options });
3142
3198
  } catch (error) {
3143
3199
  problems.push({
3144
3200
  file: module2.file,
@@ -3150,17 +3206,30 @@ async function loadLowerings(modules) {
3150
3206
  }
3151
3207
 
3152
3208
  // src/bundle.ts
3209
+ var WRITTEN_BY_TILUA = "-- Written by tilua";
3153
3210
  async function bundle(options) {
3154
- const entry = (0, import_node_path2.resolve)(options.entry);
3155
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");
3156
3213
  const inline = options.config !== void 0 && typeof options.config !== "string";
3157
- 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()));
3158
3215
  for (const p of problems) diagnostics.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
3159
- 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));
3160
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');
3161
3229
  const sources = /* @__PURE__ */ new Map();
3162
3230
  const directives = /* @__PURE__ */ new Map();
3163
- 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) || "."}`);
3164
3233
  while (queue.length) {
3165
3234
  const file = queue.shift();
3166
3235
  if (sources.has(file)) continue;
@@ -3178,6 +3247,22 @@ async function bundle(options) {
3178
3247
  if (target && !target.endsWith(".d.tilua")) queue.push(target);
3179
3248
  }
3180
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
+ }
3181
3266
  const analysis = analyzeModules([...sources.values()], config);
3182
3267
  const { lowerings, problems: loweringProblems } = await loadLowerings(analysis.lowerings);
3183
3268
  for (const p of loweringProblems) {
@@ -3188,19 +3273,56 @@ async function bundle(options) {
3188
3273
  const G = names.fresh("G");
3189
3274
  const requireExpression = member(identifier(G), "require");
3190
3275
  const importExpression = member(identifier(G), "import");
3191
- const mode = options.output ?? config?.output ?? "single";
3192
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;
3193
3313
  const modules = /* @__PURE__ */ new Map();
3194
3314
  const origins = /* @__PURE__ */ new WeakMap();
3195
3315
  const shared = new SharedRuntime(names);
3196
3316
  const shaking = options.treeShaking ?? config?.treeShaking ?? false;
3197
- const mapping = options.lineMap ?? config?.lineMap ?? true;
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);
3198
3320
  const resolveIn = (file) => (specifier) => {
3199
3321
  const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
3200
3322
  return found !== void 0 && sources.has(found) ? found : void 0;
3201
3323
  };
3202
- const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
3203
- 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];
3204
3326
  while (pending.length) {
3205
3327
  const file = pending.shift();
3206
3328
  if (modules.has(file)) continue;
@@ -3236,6 +3358,13 @@ async function bundle(options) {
3236
3358
  lines: !mapping ? void 0 : mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
3237
3359
  fail: mapping ? `${G}.fail` : void 0,
3238
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
+ },
3239
3368
  resolve: (specifier, dynamic) => {
3240
3369
  const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
3241
3370
  if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
@@ -3287,6 +3416,102 @@ async function bundle(options) {
3287
3416
  return { files: [], modules: moduleNames, diagnostics };
3288
3417
  }
3289
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
+ }
3290
3515
  const several = mode !== "single";
3291
3516
  const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
3292
3517
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
@@ -3294,7 +3519,7 @@ async function bundle(options) {
3294
3519
  for (const file of layout.slice(1)) {
3295
3520
  for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
3296
3521
  }
3297
- const program2 = (0, import_luau_parser2.parse)(runtime(G, {
3522
+ const program2 = (0, import_luau_parser3.parse)(runtime(G, {
3298
3523
  several,
3299
3524
  byPath,
3300
3525
  files: elsewhere,
@@ -3304,8 +3529,8 @@ async function bundle(options) {
3304
3529
  const assignment = program2.body.statements[1];
3305
3530
  if (usesScriptArgs) {
3306
3531
  program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
3307
- program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
3308
- 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);
3309
3534
  }
3310
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;
3311
3536
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
@@ -3318,31 +3543,40 @@ async function bundle(options) {
3318
3543
  const result2 = names.fresh("result");
3319
3544
  const entryName = (0, import_parser7.luauString)(sources.get(entry).name);
3320
3545
  const returnsExports = modules.get(entry).info.exports;
3321
- const entryStatements = (0, import_luau_parser2.parse)(!mapping ? `${returnsExports ? "return " : ""}${G}.require(${entryName})` : `
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})`}` : `
3322
3551
  local ${ok}, ${result2} = xpcall(function()
3323
3552
  return ${G}.require(${entryName})
3324
3553
  end, ${G}.fail)
3325
3554
  if not ${ok} then
3326
3555
  error(${result2}, 0)
3327
3556
  end
3328
- ${returnsExports ? `return ${result2}` : ""}
3557
+ ${declarations}
3558
+ ${returned ? `return ${returned.value}` : ""}
3329
3559
  `).body.statements;
3330
3560
  program2.body.statements.push(...entryStatements);
3331
- const map = mapping ? buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins) : /* @__PURE__ */ new Map();
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();
3332
3564
  if (mapping && (map.size || several)) {
3333
3565
  const mapStatements = [assign(
3334
3566
  [member(identifier(G), "lines")],
3335
- [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map, `${G}.lineMap`))]
3567
+ [(0, import_luau_parser3.parseExpressionFromSource)(lineMapSource(map, `${G}.lineMap`))]
3336
3568
  )];
3337
3569
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
3338
3570
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
3339
3571
  }
3340
3572
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
3341
- 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) }];
3342
3576
  for (const file of layout.slice(1)) {
3343
3577
  const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3344
3578
  const body = [...fileHelpers.declare];
3345
- 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);
3346
3580
  for (const module2 of file.modules) {
3347
3581
  body.push(assign(
3348
3582
  [index(member(identifier(G), "modules"), string(module2.name))],
@@ -3351,14 +3585,34 @@ ${returnsExports ? `return ${result2}` : ""}
3351
3585
  }
3352
3586
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
3353
3587
  if (mapping) {
3354
- const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
3588
+ const lines = buildLineMap(chunk, (0, import_luau_parser3.parse)((0, import_luau_parser3.print)(chunk, printing)), origins);
3355
3589
  body.push(...mapRegistration(G, file, lineMapSource(lines, `${G}.lineMap`)));
3356
3590
  }
3357
3591
  body.push(...fileHelpers.define);
3358
- files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
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) });
3359
3595
  }
3360
3596
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
3361
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
+ }
3362
3616
  function moduleTable(module2) {
3363
3617
  const { names: exported, links, stars } = module2.info;
3364
3618
  const fields = [];
@@ -3378,10 +3632,6 @@ function layoutOf(mode, entry, modules, outFile) {
3378
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);
3379
3633
  const others = modules.filter((m) => m !== entry);
3380
3634
  if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry, ...others] }];
3381
- if (mode === "modules") {
3382
- const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
3383
- return [entry, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
3384
- }
3385
3635
  const byName = new Map(modules.map((m) => [m.name, m]));
3386
3636
  const reach = (root) => {
3387
3637
  const seen = /* @__PURE__ */ new Set();
@@ -3417,16 +3667,75 @@ function layoutOf(mode, entry, modules, outFile) {
3417
3667
  return files;
3418
3668
  }
3419
3669
  function loadPath(entryPath, file, byPath) {
3420
- const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
3421
- 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);
3422
3705
  const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
3423
3706
  return bare.startsWith("../") ? bare : `./${bare}`;
3424
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
+ }
3425
3734
  function mapRegistration(G, file, map) {
3426
3735
  const extension = import_node_path2.posix.extname(file.path);
3427
3736
  const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
3428
3737
  const entry = `${G}.sources[#${G}.sources + 1]`;
3429
- return (0, import_luau_parser2.parse)([
3738
+ return (0, import_luau_parser3.parse)([
3430
3739
  `${entry} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
3431
3740
  ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
3432
3741
  ].join("\n")).body.statements;
@@ -3453,6 +3762,7 @@ function loader(G, byPath) {
3453
3762
  }
3454
3763
  function runtime(G, options) {
3455
3764
  const { several, mapping } = options;
3765
+ const perFile = options.perFile ?? false;
3456
3766
  const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
3457
3767
  return `
3458
3768
  local ${G}
@@ -3664,32 +3974,60 @@ ${several ? `
3664
3974
  return map[key]
3665
3975
  end })
3666
3976
  end,` : ""}
3667
- records = {},${several ? `
3977
+ records = {},${several && !perFile ? `
3668
3978
  -- The file each module outside this one is in, as \`load\` is handed it.
3669
3979
  files = { ${files} },
3670
- loaded = {},${mapping ? `
3980
+ loaded = {},` : ""}${several && mapping ? `
3671
3981
  -- Every file's line map: by the module, and by the file's name.
3672
3982
  maps = {},
3673
- sources = {},` : ""}
3983
+ sources = {},` : ""}${several && !perFile ? `
3674
3984
  -- Another of the bundle's files: a function that adds its modules here.
3675
3985
  load = function(file)
3676
3986
  if ${G}.loaded[file] then
3677
3987
  return
3678
3988
  end
3679
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
3680
4006
  end,` : ""}${options.imports ? `
3681
4007
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3682
4008
  -- rather than before its module does. \`require\` does the rest \u2014 and
3683
4009
  -- loads the file the module is in, if it is not this one.
3684
- import = function(name)
3685
- return ${G}.require(name)
4010
+ import = function(name${perFile ? ", load" : ""})
4011
+ return ${G}.require(name${perFile ? ", load" : ""})
3686
4012
  end,` : ""}
3687
- require = function(name)
4013
+ require = function(name${perFile ? ", load" : ""})
3688
4014
  local record = ${G}.records[name]
3689
4015
  if record ~= nil then
3690
4016
  return record.exports
3691
4017
  end
3692
- 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 ? `
3693
4031
  if module == nil then
3694
4032
  ${G}.load(${G}.files[name])
3695
4033
  module = ${G}.modules[name]
@@ -3777,6 +4115,35 @@ function importedSpecifiers(program2) {
3777
4115
  return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
3778
4116
  }
3779
4117
  var libraryCache = /* @__PURE__ */ new Map();
4118
+ var treeCache;
4119
+ function instanceTree(config, libs, noLib) {
4120
+ const tree = (0, import_parser7.sourceMapOf)(config);
4121
+ if (!tree) return void 0;
4122
+ if ("problem" in tree) return tree.named ? { problem: { file: config.path, message: tree.problem } } : void 0;
4123
+ const declares = (name) => libs.some((lib) => lib.body.statements.some((s) => s.type === "DeclareStatement" && s.name === name));
4124
+ if (!tree.named && !declares("game")) return void 0;
4125
+ if (treeCache && treeCache.text === tree.text && treeCache.libs.length === libs.length && treeCache.libs.every((lib, i) => lib === libs[i])) {
4126
+ return { types: treeCache.types };
4127
+ }
4128
+ const empty = (0, import_parser7.parse)("");
4129
+ const aliases = (0, import_parser7.analyzeTypes)(empty, (0, import_parser7.analyzeScopes)(empty, { noLib }), { libs, noLib, diagnostics: false }).aliases;
4130
+ const membersOf = (className) => {
4131
+ const type = aliases.get(className);
4132
+ return new Set(type?.kind === "object" ? type.properties.keys() : ["Name", "ClassName", "Parent", "Archivable"]);
4133
+ };
4134
+ const { types, problem } = (0, import_parser7.sourceMapTypes)(tree.text, tree.path, { classes: new Set(aliases.keys()), membersOf });
4135
+ if (!types) return tree.named && problem ? { problem: { file: tree.path, message: problem } } : void 0;
4136
+ const scripts = /* @__PURE__ */ new Map();
4137
+ const cached = {
4138
+ program: types.program,
4139
+ scriptFor: (file) => {
4140
+ if (!scripts.has(file)) scripts.set(file, types.scriptFor(file));
4141
+ return scripts.get(file);
4142
+ }
4143
+ };
4144
+ treeCache = { text: tree.text, libs: [...libs], types: cached, scripts };
4145
+ return { types: cached };
4146
+ }
3780
4147
  function parsedLibrary(file) {
3781
4148
  let stamp;
3782
4149
  try {
@@ -3795,11 +4162,15 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
3795
4162
  function analyzeModules(modules, config) {
3796
4163
  const out = [];
3797
4164
  const analyses = /* @__PURE__ */ new Map();
3798
- const libraries = config ? (0, import_parser7.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
4165
+ const libraries = config ? (0, import_parser7.resolveTypeLibraries)(config) : { files: [], lowerings: [], options: [], problems: [] };
3799
4166
  for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
3800
4167
  const libs = libraries.files.map(parsedLibrary);
3801
4168
  const noLib = config?.noLib ?? false;
3802
4169
  const checked = libs.length > 0 || !noLib;
4170
+ const tree = config ? instanceTree(config, libs, noLib) : void 0;
4171
+ if (tree?.problem) out.push({ file: tree.problem.file, message: tree.problem.message, line: 1, column: 1, category: "config" });
4172
+ if (tree?.types) libs.push(tree.types.program);
4173
+ const scriptOf = (file) => tree?.types && config ? tree.types.scriptFor((0, import_parser7.outputPathOf)(config, file)) : void 0;
3803
4174
  const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
3804
4175
  const byFile = new Map(modules.map((m) => [m.file, m]));
3805
4176
  const exportsCache = /* @__PURE__ */ new Map();
@@ -3812,7 +4183,9 @@ function analyzeModules(modules, config) {
3812
4183
  const done = analyzed.get(file);
3813
4184
  if (done) return done;
3814
4185
  const remembered = moduleAnalyses.get(file);
3815
- if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read3, stamp]) => stampOf(read3) === stamp)) {
4186
+ const script = scriptOf(file);
4187
+ const ownLibs = script ? [...libs, script] : libs;
4188
+ if (remembered && remembered.setting === settingFor && ownLibs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === ownLibs[i]) && remembered.program === program2 && [...remembered.read].every(([read3, stamp]) => stampOf(read3) === stamp)) {
3816
4189
  analyzed.set(file, remembered.result);
3817
4190
  readWhile.set(file, remembered.read);
3818
4191
  return remembered.result;
@@ -3825,7 +4198,7 @@ function analyzeModules(modules, config) {
3825
4198
  }
3826
4199
  const before = partials;
3827
4200
  const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
3828
- libs,
4201
+ libs: ownLibs,
3829
4202
  resolveModule: resolverFor(file),
3830
4203
  noLib,
3831
4204
  reportUnknownTypes: checked
@@ -3834,7 +4207,7 @@ function analyzeModules(modules, config) {
3834
4207
  const at = d.node;
3835
4208
  said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type", severity: d.severity });
3836
4209
  }
3837
- const result2 = { types, diagnostics: said };
4210
+ const result2 = { types, diagnostics: said, scopes };
3838
4211
  if (partials === before) {
3839
4212
  analyzed.set(file, result2);
3840
4213
  const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
@@ -3842,7 +4215,7 @@ function analyzeModules(modules, config) {
3842
4215
  if (own !== void 0) read3.set(file, own);
3843
4216
  readWhile.set(file, read3);
3844
4217
  if ([...read3.values()].every((stamp) => stamp !== void 0)) {
3845
- moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
4218
+ moduleAnalyses.set(file, { setting: settingFor, libs: [...ownLibs], program: program2, read: read3, result: result2 });
3846
4219
  }
3847
4220
  }
3848
4221
  return result2;
@@ -3891,7 +4264,16 @@ function analyzeModules(modules, config) {
3891
4264
  analyses.set(module2.file, types);
3892
4265
  out.push(...diagnostics);
3893
4266
  }
3894
- return { types: analyses, diagnostics: out, lowerings: [...libraries.lowerings] };
4267
+ const exportsOf = (file) => {
4268
+ const cached = exportsCache.get(file);
4269
+ if (cached) return cached;
4270
+ const module2 = byFile.get(file);
4271
+ const { types, scopes } = analyzeModule(file, module2.program);
4272
+ const exports2 = (0, import_parser7.moduleExports)(module2.program, scopes, types, resolverFor(file));
4273
+ exportsCache.set(file, exports2);
4274
+ return exports2;
4275
+ };
4276
+ return { types: analyses, diagnostics: out, lowerings: [...libraries.lowerings], exportsOf };
3895
4277
  }
3896
4278
  function allStatements(statements) {
3897
4279
  const out = [];
@@ -3914,7 +4296,6 @@ function allStatements(statements) {
3914
4296
  var import_node_fs4 = require("fs");
3915
4297
  var import_node_path3 = require("path");
3916
4298
  var import_parser8 = require("@tilua/parser");
3917
- var import_luau_parser3 = require("luau-parser");
3918
4299
  async function compile(source, config) {
3919
4300
  let program2;
3920
4301
  try {
@@ -3934,7 +4315,7 @@ async function compile(source, config) {
3934
4315
  line: d.node.line.start,
3935
4316
  column: d.node.column.start
3936
4317
  }));
3937
- const libraries = resolved ? (0, import_parser8.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
4318
+ const libraries = resolved ? (0, import_parser8.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], options: [], problems: [] };
3938
4319
  const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
3939
4320
  for (const p of [...problems, ...libraries.problems]) {
3940
4321
  diagnostics.push({ message: p.message, line: p.line ?? 1, column: p.column ?? 1 });
@@ -3949,10 +4330,16 @@ async function compile(source, config) {
3949
4330
  });
3950
4331
  diagnostics.push(...lowered.diagnostics);
3951
4332
  if (diagnostics.length) return { diagnostics };
3952
- return { code: (0, import_luau_parser3.print)(program(lowered.statements)) + "\n", diagnostics };
4333
+ const printed = printOutput(program(lowered.statements), printOptions(syntaxOf(lowerings), {
4334
+ indent: resolved?.indent,
4335
+ minify: resolved?.minify
4336
+ }));
4337
+ if ("problem" in printed) return { diagnostics: [{ message: printed.problem, line: 1, column: 1 }] };
4338
+ return { code: printed.code + "\n", diagnostics };
3953
4339
  }
3954
4340
  // Annotate the CommonJS export names for ESM import in node:
3955
4341
  0 && (module.exports = {
4342
+ WRITTEN_BY_TILUA,
3956
4343
  bundle,
3957
4344
  compile,
3958
4345
  loadLowerings,