luaut-parser 1.2.0 → 2.1.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
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BinaryOperators: () => BinaryOperators,
24
+ CONFIG_FILE_NAMES: () => CONFIG_FILE_NAMES,
24
25
  Keywords: () => Keywords,
25
26
  LexError: () => LexError,
26
27
  Operators: () => Operators,
@@ -35,25 +36,25 @@ __export(index_exports, {
35
36
  bufferType: () => bufferType,
36
37
  containsTypeParam: () => containsTypeParam,
37
38
  default: () => index_default,
38
- defaultLibs: () => defaultLibs,
39
39
  difference: () => difference,
40
40
  equalTypes: () => equalTypes,
41
41
  falsyType: () => falsyType,
42
+ findConfig: () => findConfig,
42
43
  fn: () => fn,
43
44
  formatType: () => formatType,
44
45
  getBinding: () => getBinding,
45
46
  intersection: () => intersection,
46
47
  isAssignable: () => isAssignable,
48
+ isClassType: () => isClassType,
47
49
  isGlobal: () => isGlobal,
48
50
  isPossiblyFalsy: () => isPossiblyFalsy,
49
51
  isPossiblyTruthy: () => isPossiblyTruthy,
50
52
  isUnassignedGlobal: () => isUnassignedGlobal,
51
53
  literal: () => literal,
52
- luauDefs: () => luauDefs,
53
- luauDefsPath: () => luauDefsPath,
54
- luauLib: () => luauLib,
54
+ loadConfig: () => loadConfig,
55
55
  luautparser: () => luautparser,
56
56
  matchInfer: () => matchInfer,
57
+ moduleCandidates: () => moduleCandidates,
57
58
  moduleExports: () => moduleExports,
58
59
  narrowExclude: () => narrowExclude,
59
60
  narrowFalsy: () => narrowFalsy,
@@ -61,6 +62,7 @@ __export(index_exports, {
61
62
  narrowTruthy: () => narrowTruthy,
62
63
  neverType: () => neverType,
63
64
  nilType: () => nilType,
65
+ nodeHost: () => nodeHost,
64
66
  numberType: () => numberType,
65
67
  objectType: () => objectType,
66
68
  optional: () => optional,
@@ -70,11 +72,12 @@ __export(index_exports, {
70
72
  parseTokens: () => parseTokens,
71
73
  parseWithRecovery: () => parseWithRecovery,
72
74
  primitive: () => primitive,
73
- robloxDefs: () => robloxDefs,
74
- robloxDefsPath: () => robloxDefsPath,
75
- robloxLib: () => robloxLib,
75
+ resolveModulePath: () => resolveModulePath,
76
+ resolveTypeLibraries: () => resolveTypeLibraries,
76
77
  setAliasExpander: () => setAliasExpander,
78
+ sourceMapTypes: () => sourceMapTypes,
77
79
  stringType: () => stringType,
80
+ stripJsonComments: () => stripJsonComments,
78
81
  substitute: () => substitute,
79
82
  templateMatches: () => templateMatches,
80
83
  threadType: () => threadType,
@@ -88,10 +91,6 @@ __export(index_exports, {
88
91
  });
89
92
  module.exports = __toCommonJS(index_exports);
90
93
 
91
- // node_modules/tsup/assets/cjs_shims.js
92
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
93
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
94
-
95
94
  // src/lexer/lexer.ts
96
95
  var Keywords = [
97
96
  "and",
@@ -946,6 +945,9 @@ var Parser = class {
946
945
  }
947
946
  if (t.type === "Identifier" && t.value === "declare") {
948
947
  const p1 = this.peek(1);
948
+ if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
949
+ return this.parseDeclareClassStatement();
950
+ }
949
951
  if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
950
952
  return this.parseDeclareStatement();
951
953
  }
@@ -987,6 +989,36 @@ var Parser = class {
987
989
  const valueType = this.parseType();
988
990
  return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
989
991
  }
992
+ /** A declared type's name. It may be qualified once — `Enum.Material` —
993
+ * which is how a definitions file names types under a namespace, and how
994
+ * they are then written (`const m: Enum.Material`). */
995
+ parseTypeName() {
996
+ const first = this.expectIdentifier();
997
+ if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
998
+ this.advance();
999
+ const second = this.expectIdentifier();
1000
+ return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
1001
+ }
1002
+ return tokenIdentifier(first);
1003
+ }
1004
+ // `declare class Name extends Base { member: T, ... }`
1005
+ parseDeclareClassStatement() {
1006
+ const start = this.current();
1007
+ this.advance();
1008
+ this.advance();
1009
+ const name = this.parseTypeName();
1010
+ let superclass;
1011
+ if (this.checkIdentifierValue("extends")) {
1012
+ this.advance();
1013
+ const base = this.parseType();
1014
+ if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
1015
+ superclass = base;
1016
+ }
1017
+ if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
1018
+ const body = this.parseTableType();
1019
+ if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
1020
+ return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
1021
+ }
990
1022
  // `import { a, b as c } from '...'` / `import Default from '...'` /
991
1023
  // `import Default, { a } from '...'`. Compiled away entirely by the
992
1024
  // bundler — never survives into emitted Luau.
@@ -1317,8 +1349,7 @@ var Parser = class {
1317
1349
  parseTypeAliasStatement() {
1318
1350
  const start = this.current();
1319
1351
  this.advance();
1320
- const nameTok = this.expectIdentifier();
1321
- const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
1352
+ const name = this.parseTypeName();
1322
1353
  let generics = [];
1323
1354
  if (this.checkOperator("<")) {
1324
1355
  generics = this.parseGenericTypeParameterList();
@@ -2820,6 +2851,9 @@ var Analyzer = class {
2820
2851
  case "DeclareStatement":
2821
2852
  this.visitType(stmt.valueType, scope);
2822
2853
  return;
2854
+ case "DeclareClassStatement":
2855
+ this.visitType(stmt.body, scope);
2856
+ return;
2823
2857
  case "TypeAliasStatement":
2824
2858
  case "ExportTypeAliasStatement":
2825
2859
  this.visitType(stmt.definition, scope);
@@ -2993,6 +3027,9 @@ function analyzeScopes(program, options = {}) {
2993
3027
  }
2994
3028
 
2995
3029
  // src/ast/typeModel.ts
3030
+ function isClassType(t) {
3031
+ return t.kind === "object" && t.class !== void 0;
3032
+ }
2996
3033
  function typeParam(name, constraint, isConst) {
2997
3034
  return { kind: "typeParam", name, constraint, isConst };
2998
3035
  }
@@ -3052,10 +3089,16 @@ function substitute(t, subst) {
3052
3089
  }
3053
3090
  case "function": {
3054
3091
  const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
3092
+ let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
3093
+ let varargs = t.varargs && substitute(t.varargs, inner);
3094
+ if (varargs?.kind === "tuple" && varargs.isPack) {
3095
+ params = [...params, ...varargs.elements.map((type) => ({ type }))];
3096
+ varargs = void 0;
3097
+ }
3055
3098
  return {
3056
3099
  kind: "function",
3057
- params: t.params.map((p) => ({ ...p, type: substitute(p.type, inner) })),
3058
- varargs: t.varargs && substitute(t.varargs, inner),
3100
+ params,
3101
+ varargs,
3059
3102
  returns: substitute(t.returns, inner),
3060
3103
  typeParams: t.typeParams,
3061
3104
  predicate: t.predicate && {
@@ -3135,7 +3178,8 @@ function unify(param, arg, vars, out) {
3135
3178
  }
3136
3179
  return;
3137
3180
  case "object":
3138
- if (arg.kind === "object") {
3181
+ if (param.class) return;
3182
+ if (arg.kind === "object" && !arg.class) {
3139
3183
  for (const [k, pv] of param.properties) {
3140
3184
  const av = arg.properties.get(k);
3141
3185
  if (av) unify(pv.type, av.type, vars, out);
@@ -3218,7 +3262,7 @@ function widen(t) {
3218
3262
  case "tuple":
3219
3263
  return tuple(t.elements.map(widen), t.isPack);
3220
3264
  case "object": {
3221
- if (t.frozen) return t;
3265
+ if (t.frozen || t.class) return t;
3222
3266
  const entries = [];
3223
3267
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
3224
3268
  const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
@@ -3245,6 +3289,10 @@ function isAssignable(rawA, rawB) {
3245
3289
  if (expandAlias) {
3246
3290
  if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
3247
3291
  else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
3292
+ else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
3293
+ a = expandAlias(a);
3294
+ b = expandAlias(b);
3295
+ }
3248
3296
  if (a === b) return true;
3249
3297
  }
3250
3298
  for (let i = 0; i < comparing.length; i += 2) {
@@ -3270,7 +3318,11 @@ function isAssignableInner(a, b) {
3270
3318
  if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
3271
3319
  if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
3272
3320
  if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
3273
- if (a.kind === "intersection") return a.types.some((t) => isAssignable(t, b));
3321
+ if (a.kind === "intersection") {
3322
+ if (a.types.some((t) => isAssignable(t, b))) return true;
3323
+ const merged = mergeObjectMembers(a.types);
3324
+ return merged !== void 0 && isAssignable(merged, b);
3325
+ }
3274
3326
  if (a.kind === "literal") {
3275
3327
  if (b.kind === "literal") return a.value === b.value;
3276
3328
  if (b.kind === "primitive") return b.name === a.base;
@@ -3296,6 +3348,8 @@ function isAssignableInner(a, b) {
3296
3348
  }
3297
3349
  if (a.kind === "object") {
3298
3350
  if (b.kind !== "object") return false;
3351
+ if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
3352
+ if (a.class && (b.indexer || b.properties.size === 0)) return false;
3299
3353
  for (const [name, bp] of b.properties) {
3300
3354
  const ap = a.properties.get(name);
3301
3355
  if (!ap) {
@@ -3438,6 +3492,7 @@ function containsFreeTypeParam(t, seen, bound) {
3438
3492
  case "intersection":
3439
3493
  return t.types.some((m) => containsTypeParam(m, seen, bound));
3440
3494
  case "object":
3495
+ if (t.class) return false;
3441
3496
  return [...t.properties.values()].some((v) => containsTypeParam(v.type, seen, bound)) || !!t.indexer && (containsTypeParam(t.indexer.key, seen, bound) || containsTypeParam(t.indexer.value, seen, bound));
3442
3497
  case "function": {
3443
3498
  const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
@@ -3625,6 +3680,39 @@ var IDENT_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
3625
3680
  function formatKey(k) {
3626
3681
  return IDENT_KEY.test(k) ? k : JSON.stringify(k);
3627
3682
  }
3683
+ function mergeObjectMembers(types) {
3684
+ const objects = [];
3685
+ const seen = /* @__PURE__ */ new Set();
3686
+ const collect = (t) => {
3687
+ if (seen.has(t)) return true;
3688
+ seen.add(t);
3689
+ if (t.kind === "genericRef") {
3690
+ const expanded = expandAlias?.(t);
3691
+ return expanded !== void 0 && expanded !== t && collect(expanded);
3692
+ }
3693
+ if (t.kind === "intersection") return t.types.every(collect);
3694
+ if (t.kind === "object" && !t.class) {
3695
+ objects.push(t);
3696
+ return true;
3697
+ }
3698
+ return false;
3699
+ };
3700
+ if (!types.every(collect) || objects.length < 2) return void 0;
3701
+ const properties = /* @__PURE__ */ new Map();
3702
+ let indexer;
3703
+ for (const object of objects) {
3704
+ indexer ??= object.indexer;
3705
+ for (const [name, property] of object.properties) {
3706
+ const existing = properties.get(name);
3707
+ properties.set(name, existing ? {
3708
+ type: intersection([existing.type, property.type]),
3709
+ optional: existing.optional && property.optional,
3710
+ readonly: existing.readonly || property.readonly
3711
+ } : property);
3712
+ }
3713
+ }
3714
+ return objectType([...properties], indexer);
3715
+ }
3628
3716
 
3629
3717
  // src/ast/analyzeTypes.ts
3630
3718
  function analyzeTypes(program, scopes, options = {}) {
@@ -3815,6 +3903,62 @@ function keepsLiterals(paramType) {
3815
3903
  const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
3816
3904
  return members.some((m) => m.kind === "literal");
3817
3905
  }
3906
+ var AliasMap = class extends Map {
3907
+ pending = /* @__PURE__ */ new Map();
3908
+ defer(name, resolve5) {
3909
+ super.delete(name);
3910
+ this.pending.set(name, resolve5);
3911
+ }
3912
+ get(name) {
3913
+ const resolved = super.get(name);
3914
+ if (resolved !== void 0) return resolved;
3915
+ const resolve5 = this.pending.get(name);
3916
+ if (!resolve5) return void 0;
3917
+ this.pending.delete(name);
3918
+ const type = resolve5();
3919
+ super.set(name, type);
3920
+ return type;
3921
+ }
3922
+ has(name) {
3923
+ return super.has(name) || (this.pending?.has(name) ?? false);
3924
+ }
3925
+ set(name, type) {
3926
+ this.pending?.delete(name);
3927
+ return super.set(name, type);
3928
+ }
3929
+ delete(name) {
3930
+ const deferred = this.pending?.delete(name) ?? false;
3931
+ return super.delete(name) || deferred;
3932
+ }
3933
+ get size() {
3934
+ return super.size + (this.pending?.size ?? 0);
3935
+ }
3936
+ keys() {
3937
+ return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
3938
+ }
3939
+ entries() {
3940
+ return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
3941
+ }
3942
+ values() {
3943
+ return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
3944
+ }
3945
+ forEach(callback, thisArg) {
3946
+ for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
3947
+ }
3948
+ [Symbol.iterator]() {
3949
+ return this.entries();
3950
+ }
3951
+ };
3952
+ var METAMETHODS = {
3953
+ "+": "__add",
3954
+ "-": "__sub",
3955
+ "*": "__mul",
3956
+ "/": "__div",
3957
+ "//": "__idiv",
3958
+ "%": "__mod",
3959
+ "^": "__pow",
3960
+ "..": "__concat"
3961
+ };
3818
3962
  function posKey(name, line, column) {
3819
3963
  return `${name}@${line}:${column}`;
3820
3964
  }
@@ -3835,9 +3979,12 @@ var TypeAnalyzer = class {
3835
3979
  expectedTypeOf = /* @__PURE__ */ new Map();
3836
3980
  /** Public: each alias resolved once (generic aliases keep their params as
3837
3981
  * `typeParam` nodes in the body). */
3838
- aliases = /* @__PURE__ */ new Map();
3982
+ aliases = new AliasMap();
3839
3983
  /** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
3840
3984
  aliasDefs = /* @__PURE__ */ new Map();
3985
+ /** See `resolveClass`. */
3986
+ classTypes = /* @__PURE__ */ new WeakMap();
3987
+ classMembers = /* @__PURE__ */ new WeakMap();
3841
3988
  /** Generic parameters currently in lexical scope (alias body / generic fn),
3842
3989
  * with their `extends` constraints resolved. */
3843
3990
  typeParamScope = [];
@@ -3947,24 +4094,123 @@ var TypeAnalyzer = class {
3947
4094
  for (const stmt of block.statements) {
3948
4095
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
3949
4096
  if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
4097
+ if (stmt.type === "DeclareClassStatement") {
4098
+ this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
4099
+ }
3950
4100
  }
3951
4101
  }
3952
- /** Seed global types from `declare` statements. Repeating a name builds an
3953
- * *overload set* (an intersection, in declaration order) rather than
3954
- * replacing which is how `typeof` gets one signature per result string. */
4102
+ /** A non-generic definition's type. */
4103
+ resolveDef(def) {
4104
+ return def.class ? this.classType(def.class) : this.resolveType(def.node);
4105
+ }
4106
+ /** One type per class declaration, so every mention of a class is the same
4107
+ * object — its own members included, which refer back to it. */
4108
+ classType(stmt) {
4109
+ return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
4110
+ }
4111
+ /** A class's members are resolved the first time anyone asks for
4112
+ * `properties` — its own from its body, the inherited ones from its
4113
+ * superclass.
4114
+ *
4115
+ * Both have to wait. A definitions file for a whole engine declares
4116
+ * thousands of classes that all refer to one another; resolving each body
4117
+ * as soon as the class is named would resolve every class on every
4118
+ * analysis, when a script touches a handful. And classes refer to one
4119
+ * another constantly — `Object.IsA` mentions a map of every class, each
4120
+ * of which extends `Object` — so while one class resolves, one it extends
4121
+ * may itself be half-resolved; copying its members then would miss some
4122
+ * for good. */
4123
+ resolveClass(stmt) {
4124
+ const name = stmt.name.name;
4125
+ const { ancestors, cyclic } = this.classChain(stmt);
4126
+ const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
4127
+ let own;
4128
+ let resolvingOwn = false;
4129
+ const ownMembers = () => {
4130
+ if (own || resolvingOwn) return own;
4131
+ resolvingOwn = true;
4132
+ try {
4133
+ own = this.resolveType(stmt.body);
4134
+ } finally {
4135
+ resolvingOwn = false;
4136
+ }
4137
+ return own;
4138
+ };
4139
+ let complete;
4140
+ const members = () => {
4141
+ if (complete) return complete;
4142
+ const mine = ownMembers();
4143
+ if (!mine) return void 0;
4144
+ const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
4145
+ if (superclass && !base) return void 0;
4146
+ return complete = {
4147
+ properties: new Map([...base?.properties ?? [], ...mine.properties]),
4148
+ indexer: mine.indexer ?? base?.indexer
4149
+ };
4150
+ };
4151
+ const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
4152
+ Object.defineProperties(type, {
4153
+ properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
4154
+ indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
4155
+ });
4156
+ this.classTypes.set(stmt, type);
4157
+ this.classMembers.set(type, members);
4158
+ if (this.program.body.statements.includes(stmt)) ownMembers();
4159
+ return type;
4160
+ }
4161
+ /** `extends` must name a class, and the chain must end. */
4162
+ checkClass(stmt) {
4163
+ if (!stmt.superclass || !this.emitDiagnostics) return;
4164
+ const base = stmt.superclass.base;
4165
+ if (!this.aliasDefs.get(base)?.class) {
4166
+ const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
4167
+ this.diagnostics.push({
4168
+ node: stmt.superclass,
4169
+ message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
4170
+ });
4171
+ } else if (this.classChain(stmt).cyclic) {
4172
+ this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
4173
+ }
4174
+ }
4175
+ /** The class and the classes it extends, nearest first, read from the
4176
+ * declarations — no type has to be resolved to know them. The walk stops
4177
+ * at a superclass that is not a class. */
4178
+ classChain(stmt) {
4179
+ const ancestors = [stmt.name.name];
4180
+ for (let cls = stmt; cls?.superclass; ) {
4181
+ const base = cls.superclass.base;
4182
+ if (ancestors.includes(base)) return { ancestors, cyclic: true };
4183
+ cls = this.aliasDefs.get(base)?.class;
4184
+ if (!cls) break;
4185
+ ancestors.push(base);
4186
+ }
4187
+ return { ancestors, cyclic: false };
4188
+ }
4189
+ /** Seed global types from `declare` statements. Repeating a function name
4190
+ * builds an *overload set* (an intersection, in declaration order) rather
4191
+ * than replacing — which is how `typeof` gets one signature per result
4192
+ * string. Any other value is simply redeclared: a sourcemap's
4193
+ * `declare script: <this file's instance>` replaces the library's
4194
+ * `declare script: LuaSourceContainer`. */
3955
4195
  harvestDeclares(block) {
3956
4196
  for (const stmt of block.statements) {
3957
4197
  if (stmt.type !== "DeclareStatement") continue;
3958
4198
  const t = this.resolveType(stmt.valueType);
3959
4199
  const prev = this.libGlobalTypes.get(stmt.name);
3960
- this.libGlobalTypes.set(stmt.name, prev ? intersection([prev, t]) : t);
4200
+ const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
4201
+ this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
3961
4202
  }
3962
4203
  }
3963
4204
  resolveAllAliases() {
3964
4205
  for (const [name, def] of this.aliasDefs) {
4206
+ if (def.class && !this.program.body.statements.includes(def.class)) {
4207
+ const cls = def.class;
4208
+ this.aliases.defer(name, () => this.classType(cls));
4209
+ continue;
4210
+ }
3965
4211
  if (containsTypeQuery(def.node)) continue;
3966
4212
  this.withTypeParams(def.params, () => {
3967
- this.aliases.set(name, this.resolveType(def.node));
4213
+ this.aliases.set(name, this.resolveDef(def));
3968
4214
  });
3969
4215
  }
3970
4216
  }
@@ -3974,7 +4220,7 @@ var TypeAnalyzer = class {
3974
4220
  for (const [name, def] of this.aliasDefs) {
3975
4221
  if (this.aliases.has(name)) continue;
3976
4222
  this.withTypeParams(def.params, () => {
3977
- this.aliases.set(name, this.resolveType(def.node));
4223
+ this.aliases.set(name, this.resolveDef(def));
3978
4224
  });
3979
4225
  }
3980
4226
  return this.aliases;
@@ -4002,10 +4248,7 @@ var TypeAnalyzer = class {
4002
4248
  /** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
4003
4249
  instantiateAlias(def, args) {
4004
4250
  if (this.instantiationDepth > 20) return unknownType;
4005
- const subst = /* @__PURE__ */ new Map();
4006
- def.params.forEach((p, i) => {
4007
- subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
4008
- });
4251
+ const subst = this.bindTypeArguments(def.params, args);
4009
4252
  this.instantiationDepth++;
4010
4253
  try {
4011
4254
  const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
@@ -4014,6 +4257,24 @@ var TypeAnalyzer = class {
4014
4257
  this.instantiationDepth--;
4015
4258
  }
4016
4259
  }
4260
+ /** Pair written type arguments with the parameters they instantiate. A
4261
+ * pack parameter (`T...`) takes every argument from its position on, as
4262
+ * one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
4263
+ * and `Signal<()>` to the empty pack. Left out, a parameter takes its
4264
+ * default (`T... = ...any` is `any`), or `unknown`. */
4265
+ bindTypeArguments(params, args) {
4266
+ const subst = /* @__PURE__ */ new Map();
4267
+ params.forEach((p, i) => {
4268
+ let arg = args[i];
4269
+ if (p.isPack && i < args.length) {
4270
+ const rest = args.slice(i);
4271
+ const single = rest.length === 1 ? rest[0] : void 0;
4272
+ arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
4273
+ }
4274
+ subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
4275
+ });
4276
+ return subst;
4277
+ }
4017
4278
  // --------------------------------------------------------
4018
4279
  // TypeNode -> Type
4019
4280
  // --------------------------------------------------------
@@ -4075,6 +4336,12 @@ var TypeAnalyzer = class {
4075
4336
  }
4076
4337
  const lib = this.options.libTypes?.[node.base];
4077
4338
  if (lib) return lib;
4339
+ } else if (this.aliasDefs.has(name)) {
4340
+ return this.expand({
4341
+ kind: "genericRef",
4342
+ name,
4343
+ typeArguments: node.typeArguments.map((a) => this.resolveType(a))
4344
+ });
4078
4345
  }
4079
4346
  return {
4080
4347
  kind: "genericRef",
@@ -4201,6 +4468,7 @@ var TypeAnalyzer = class {
4201
4468
  return this.resolveType(node.typeAnnotation);
4202
4469
  case "TypePackNode": {
4203
4470
  if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
4471
+ if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
4204
4472
  return tuple(node.types.map((t) => this.resolveType(t)), true);
4205
4473
  }
4206
4474
  }
@@ -4224,7 +4492,7 @@ var TypeAnalyzer = class {
4224
4492
  this.reduceDepth++;
4225
4493
  try {
4226
4494
  const result = this.reduceTypeInner(t);
4227
- this.reduceCache.set(t, result);
4495
+ if (result.kind !== "keyof") this.reduceCache.set(t, result);
4228
4496
  return result;
4229
4497
  } finally {
4230
4498
  this.reduceDepth--;
@@ -4236,6 +4504,7 @@ var TypeAnalyzer = class {
4236
4504
  case "keyof": {
4237
4505
  const target = this.reduceType(t.target);
4238
4506
  if (containsTypeParam(target)) return { kind: "keyof", target };
4507
+ if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
4239
4508
  return this.keysOf(target);
4240
4509
  }
4241
4510
  case "indexedAccess": {
@@ -4277,6 +4546,7 @@ var TypeAnalyzer = class {
4277
4546
  t.predicate
4278
4547
  );
4279
4548
  case "object": {
4549
+ if (t.class) return t;
4280
4550
  const entries = [];
4281
4551
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
4282
4552
  const reduced = objectType(entries, t.indexer && {
@@ -4416,6 +4686,7 @@ var TypeAnalyzer = class {
4416
4686
  t.typeParams
4417
4687
  );
4418
4688
  case "object": {
4689
+ if (t.class) return t;
4419
4690
  const entries = [];
4420
4691
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
4421
4692
  return objectType(entries, t.indexer && {
@@ -4469,6 +4740,11 @@ var TypeAnalyzer = class {
4469
4740
  visitStatement(stmt, env) {
4470
4741
  switch (stmt.type) {
4471
4742
  case "VariableDeclaration": {
4743
+ stmt.names.forEach((target, i) => {
4744
+ if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
4745
+ this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
4746
+ }
4747
+ });
4472
4748
  const { types: valueTypes, sources } = this.valueList(stmt.init, env);
4473
4749
  stmt.names.forEach((target, i) => {
4474
4750
  const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
@@ -4529,6 +4805,16 @@ var TypeAnalyzer = class {
4529
4805
  return;
4530
4806
  }
4531
4807
  case "AssignmentStatement": {
4808
+ stmt.targets.forEach((target, i) => {
4809
+ const value = stmt.values[i];
4810
+ if (!value) return;
4811
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
4812
+ this.applyContext(value, this.infer(target, env));
4813
+ } else if (target.type === "Identifier") {
4814
+ const id = this.bindingIdOf(target);
4815
+ if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
4816
+ }
4817
+ });
4532
4818
  const { types: valueTypes, sources } = this.valueList(stmt.values, env);
4533
4819
  stmt.targets.forEach((target, i) => {
4534
4820
  const vt = valueTypes[i] ?? unknownType;
@@ -4673,6 +4959,9 @@ var TypeAnalyzer = class {
4673
4959
  case "BreakStatement":
4674
4960
  this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
4675
4961
  return;
4962
+ case "DeclareClassStatement":
4963
+ this.checkClass(stmt);
4964
+ return;
4676
4965
  case "ContinueStatement":
4677
4966
  case "TypeAliasStatement":
4678
4967
  case "ExportTypeAliasStatement":
@@ -4794,7 +5083,46 @@ var TypeAnalyzer = class {
4794
5083
  }
4795
5084
  if (p.pattern) return this.patternToType(p.pattern, env);
4796
5085
  if (p.default) return widen(this.infer(p.default, env));
4797
- return anyType;
5086
+ return this.contextualParams.get(p) ?? anyType;
5087
+ }
5088
+ /** What a function expression's unannotated parameters are, from where
5089
+ * it is written — see `applyContext`. */
5090
+ contextualParams = /* @__PURE__ */ new WeakMap();
5091
+ /** `expected` is the type the surroundings want for `expr`. A function
5092
+ * expression written there takes its unannotated parameters' types from
5093
+ * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
5094
+ * `player` from `Connect`'s callback type. Anything else is inferred as
5095
+ * usual. */
5096
+ applyContext(expr, expected) {
5097
+ let e = expr;
5098
+ while (e.type === "ParenthesizedExpression") e = e.expression;
5099
+ if (e.type !== "FunctionExpression" || !expected) return;
5100
+ const members = expected.kind === "union" ? expected.types : [expected];
5101
+ const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
5102
+ if (!signatures.length) return;
5103
+ e.func.params.forEach((p, k) => {
5104
+ if (p.typeAnnotation || p.pattern || p.default) return;
5105
+ const candidates = [];
5106
+ for (const signature of signatures) {
5107
+ const t2 = signature.params[k]?.type ?? signature.varargs;
5108
+ if (t2) candidates.push(t2);
5109
+ }
5110
+ if (!candidates.length) return;
5111
+ const t = union(candidates);
5112
+ this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
5113
+ });
5114
+ }
5115
+ /** The parameter type each written argument lands on, across `fns`. */
5116
+ expectedArguments(written, fns, selfOf) {
5117
+ return written.map((_, j) => {
5118
+ const candidates = [];
5119
+ for (const f of fns) {
5120
+ const i = j + selfOf(f);
5121
+ const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
5122
+ if (param) candidates.push(param);
5123
+ }
5124
+ return candidates.length ? union(candidates) : void 0;
5125
+ });
4798
5126
  }
4799
5127
  /** Synthesize a type from a destructuring pattern used without an
4800
5128
  * annotation (`function f({ a, b = 1 })`). */
@@ -4846,7 +5174,8 @@ var TypeAnalyzer = class {
4846
5174
  f.params.forEach((p, i) => {
4847
5175
  const arg = argTypes[i];
4848
5176
  if (arg === void 0) return;
4849
- unify(p.type, keepsLiterals(p.type) ? arg : widen(arg), vars, subst);
5177
+ const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
5178
+ unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
4850
5179
  });
4851
5180
  for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
4852
5181
  return subst;
@@ -4907,12 +5236,13 @@ var TypeAnalyzer = class {
4907
5236
  return;
4908
5237
  }
4909
5238
  const t = value;
5239
+ if (t.kind === "object" && t.class) return;
4910
5240
  if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
4911
5241
  bounds.set(t.name, this.reduceType(t.constraint));
4912
5242
  }
4913
5243
  for (const child of Object.values(value)) walk(child);
4914
5244
  };
4915
- for (const p of f.params) walk(p.type);
5245
+ for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
4916
5246
  return f.params.map((p) => substitute(p.type, bounds));
4917
5247
  }
4918
5248
  /** Record what each written argument is expected to be — see
@@ -5251,7 +5581,7 @@ var TypeAnalyzer = class {
5251
5581
  this.expandCache.set(key, t);
5252
5582
  this.resolvingAliases.add(t.name);
5253
5583
  try {
5254
- const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveType(def.node);
5584
+ const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
5255
5585
  const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
5256
5586
  this.expandCache.set(key, named);
5257
5587
  return named;
@@ -5374,9 +5704,9 @@ var TypeAnalyzer = class {
5374
5704
  case "not":
5375
5705
  return booleanType;
5376
5706
  case "-":
5377
- return numberType;
5707
+ return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
5378
5708
  case "#":
5379
- return numberType;
5709
+ return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
5380
5710
  }
5381
5711
  return arg;
5382
5712
  }
@@ -5398,7 +5728,7 @@ var TypeAnalyzer = class {
5398
5728
  const r = this.infer(expr.right, env);
5399
5729
  switch (op) {
5400
5730
  case "..":
5401
- return stringType;
5731
+ return this.operatorResult(expr, op, l, r) ?? stringType;
5402
5732
  case "==":
5403
5733
  case "~=":
5404
5734
  case "<":
@@ -5413,7 +5743,7 @@ var TypeAnalyzer = class {
5413
5743
  case "//":
5414
5744
  case "%":
5415
5745
  case "^":
5416
- return numberType;
5746
+ return this.operatorResult(expr, op, l, r) ?? numberType;
5417
5747
  }
5418
5748
  return union([l, r]);
5419
5749
  }
@@ -5432,8 +5762,10 @@ var TypeAnalyzer = class {
5432
5762
  }
5433
5763
  case "CallExpression": {
5434
5764
  const callee = this.infer(expr.callee, env);
5435
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5436
5765
  const fns = this.overloadsOf(callee);
5766
+ const expected = this.expectedArguments(expr.arguments, fns, () => 0);
5767
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5768
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
5437
5769
  if (fns.length) {
5438
5770
  this.recordExpected(expr.arguments, fns, () => 0);
5439
5771
  const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
@@ -5448,8 +5780,10 @@ var TypeAnalyzer = class {
5448
5780
  }
5449
5781
  case "MethodCallExpression": {
5450
5782
  const objType = this.infer(expr.object, env);
5451
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5452
5783
  const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
5784
+ const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
5785
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5786
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
5453
5787
  if (fns.length) {
5454
5788
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5455
5789
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -5841,6 +6175,35 @@ var TypeAnalyzer = class {
5841
6175
  this.selfType = saved;
5842
6176
  }
5843
6177
  }
6178
+ /** What an operator on a value with metamethods gives: `a + b` calls
6179
+ * `__add` on `a`, or failing that on `b` with the operands swapped — the
6180
+ * order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
6181
+ * Vector3` and `2 * vector` get their types from the declarations.
6182
+ * `undefined` when neither operand declares the metamethod; an operand
6183
+ * that declares it but accepts neither argument is reported. */
6184
+ operatorResult(node, op, left, right) {
6185
+ const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
6186
+ if (!name) return void 0;
6187
+ const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
6188
+ let declared;
6189
+ for (const [receiver, other] of candidates) {
6190
+ const t = this.expand(receiver);
6191
+ const method = t.kind === "object" ? t.properties.get(name) : void 0;
6192
+ if (!method) continue;
6193
+ declared ??= receiver;
6194
+ const args = other === void 0 ? [receiver] : [receiver, other];
6195
+ const picked = this.pickOverload(this.overloadsOf(method.type), args);
6196
+ if (picked) return this.callReturn(picked, args);
6197
+ }
6198
+ if (declared && this.emitDiagnostics) {
6199
+ this.diagnostics.push({
6200
+ node,
6201
+ message: right === void 0 ? `Operator '${op}' cannot be applied to type '${formatType(left)}'` : `Operator '${op}' cannot be applied to types '${formatType(left)}' and '${formatType(right)}'`
6202
+ });
6203
+ return anyType;
6204
+ }
6205
+ return void 0;
6206
+ }
5844
6207
  /** Does this signature take the receiver as its first parameter?
5845
6208
  *
5846
6209
  * Luau's `:` is sugar both ways: `function T:m(a)` declares
@@ -5917,22 +6280,366 @@ function briefType(t) {
5917
6280
  return formatType(t);
5918
6281
  }
5919
6282
 
5920
- // src/lib/luau.ts
6283
+ // src/project/host.ts
5921
6284
  var import_node_fs = require("fs");
5922
- var import_node_url = require("url");
5923
- var luauDefsPath = (0, import_node_url.fileURLToPath)(new URL("./luau.d.luaut", importMetaUrl));
5924
- var luauDefs = (0, import_node_fs.readFileSync)(luauDefsPath, "utf8");
5925
- var luauLib = parse(luauDefs);
6285
+ var nodeHost = {
6286
+ readFile(path) {
6287
+ try {
6288
+ return (0, import_node_fs.statSync)(path).isFile() ? (0, import_node_fs.readFileSync)(path, "utf8") : void 0;
6289
+ } catch {
6290
+ return void 0;
6291
+ }
6292
+ }
6293
+ };
6294
+
6295
+ // src/project/config.ts
6296
+ var import_node_path = require("path");
6297
+ var CONFIG_FILE_NAMES = ["luaut.config.json", "luaut.config.jsonc"];
6298
+ function findConfig(file, host = nodeHost) {
6299
+ const searched = [];
6300
+ let directory = (0, import_node_path.dirname)((0, import_node_path.resolve)(file));
6301
+ for (; ; ) {
6302
+ const found = [];
6303
+ for (const name of CONFIG_FILE_NAMES) {
6304
+ const path = (0, import_node_path.join)(directory, name);
6305
+ searched.push(path);
6306
+ if (host.readFile(path) !== void 0) found.push(path);
6307
+ }
6308
+ if (found.length > 1) {
6309
+ const message = `Only one luaut config may be in a folder, but both ${CONFIG_FILE_NAMES.join(" and ")} are in ${directory}`;
6310
+ return { searched, problems: found.map((path) => ({ file: path, message, line: 1, column: 1 })) };
6311
+ }
6312
+ if (found.length === 1) {
6313
+ const { config, problems } = loadConfig(found[0], host);
6314
+ return { config, problems, searched };
6315
+ }
6316
+ const parent = (0, import_node_path.dirname)(directory);
6317
+ if (parent === directory) return { searched, problems: [] };
6318
+ directory = parent;
6319
+ }
6320
+ }
6321
+ var OPTIONS = ["types", "paths", "baseUrl", "sourceMap"];
6322
+ function loadConfig(path, host = nodeHost) {
6323
+ const file = (0, import_node_path.resolve)(path);
6324
+ const source = host.readFile(file);
6325
+ if (source === void 0) return { problems: [{ file, message: "Cannot read the config file" }] };
6326
+ let raw;
6327
+ try {
6328
+ raw = JSON.parse(stripJsonComments(source));
6329
+ } catch (error) {
6330
+ const message = error.message;
6331
+ return { problems: [{ file, message: `Invalid JSON: ${message}`, ...jsonErrorPosition(source, message) }] };
6332
+ }
6333
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
6334
+ return { problems: [{ file, message: "The config must be a JSON object", line: 1, column: 1 }] };
6335
+ }
6336
+ const directory = (0, import_node_path.dirname)(file);
6337
+ const options = raw;
6338
+ const problems = [];
6339
+ const at = (key) => keyPosition(source, key);
6340
+ const problem = (key, message) => {
6341
+ problems.push({ file, message, ...at(key) });
6342
+ };
6343
+ for (const key of Object.keys(options)) {
6344
+ if (!OPTIONS.includes(key)) {
6345
+ problem(key, `Unknown option '${key}'. Options are: ${OPTIONS.join(", ")}`);
6346
+ }
6347
+ }
6348
+ let types = [];
6349
+ if (options.types !== void 0) {
6350
+ if (Array.isArray(options.types) && options.types.every((t) => typeof t === "string")) types = options.types;
6351
+ else problem("types", `'types' must be an array of strings, such as ["luau"]`);
6352
+ }
6353
+ const paths = {};
6354
+ if (options.paths !== void 0) {
6355
+ const value = options.paths;
6356
+ if (value && typeof value === "object" && !Array.isArray(value)) {
6357
+ for (const [pattern, targets] of Object.entries(value)) {
6358
+ if (Array.isArray(targets) && targets.every((t) => typeof t === "string")) paths[pattern] = targets;
6359
+ else problem(pattern, `'paths' entry '${pattern}' must be an array of strings`);
6360
+ if (pattern.split("*").length > 2) problem(pattern, `'paths' pattern '${pattern}' may contain at most one '*'`);
6361
+ }
6362
+ } else {
6363
+ problem("paths", `'paths' must be an object, such as { "@shared/*": ["src/shared/*"] }`);
6364
+ }
6365
+ }
6366
+ let baseUrl = directory;
6367
+ if (options.baseUrl !== void 0) {
6368
+ if (typeof options.baseUrl === "string") baseUrl = (0, import_node_path.resolve)(directory, options.baseUrl);
6369
+ else problem("baseUrl", "'baseUrl' must be a string");
6370
+ }
6371
+ let sourceMap = null;
6372
+ if (options.sourceMap !== void 0 && options.sourceMap !== null) {
6373
+ if (typeof options.sourceMap === "string") sourceMap = (0, import_node_path.resolve)(directory, options.sourceMap);
6374
+ else problem("sourceMap", "'sourceMap' must be a path string, or null for none");
6375
+ }
6376
+ return { config: { path: file, directory, source, types, paths, baseUrl, sourceMap }, problems };
6377
+ }
6378
+ function stripJsonComments(text) {
6379
+ const out = text.split("");
6380
+ let i = 0;
6381
+ let inString = false;
6382
+ while (i < text.length) {
6383
+ const ch = text[i];
6384
+ if (inString) {
6385
+ if (ch === "\\") i += 2;
6386
+ else {
6387
+ if (ch === '"') inString = false;
6388
+ i++;
6389
+ }
6390
+ continue;
6391
+ }
6392
+ if (ch === '"') {
6393
+ inString = true;
6394
+ i++;
6395
+ } else if (ch === "/" && text[i + 1] === "/") {
6396
+ while (i < text.length && text[i] !== "\n") out[i++] = " ";
6397
+ } else if (ch === "/" && text[i + 1] === "*") {
6398
+ out[i++] = " ";
6399
+ out[i++] = " ";
6400
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
6401
+ if (text[i] !== "\n") out[i] = " ";
6402
+ i++;
6403
+ }
6404
+ if (i < text.length) {
6405
+ out[i++] = " ";
6406
+ out[i++] = " ";
6407
+ }
6408
+ } else if (ch === ",") {
6409
+ let j = i + 1;
6410
+ while (j < text.length && /\s/.test(text[j])) j++;
6411
+ if (text[j] === "}" || text[j] === "]") out[i] = " ";
6412
+ i++;
6413
+ } else {
6414
+ i++;
6415
+ }
6416
+ }
6417
+ return out.join("");
6418
+ }
6419
+ function jsonErrorPosition(source, message) {
6420
+ const lineColumn = /line (\d+) column (\d+)/.exec(message);
6421
+ if (lineColumn) return { line: Number(lineColumn[1]), column: Number(lineColumn[2]) };
6422
+ const position = /position (\d+)/.exec(message);
6423
+ return position ? offsetPosition(source, Number(position[1])) : { line: 1, column: 1 };
6424
+ }
6425
+ function keyPosition(source, key) {
6426
+ const offset = source.indexOf(JSON.stringify(key));
6427
+ return offset < 0 ? { line: 1, column: 1 } : offsetPosition(source, offset);
6428
+ }
6429
+ function offsetPosition(source, offset) {
6430
+ const before = source.slice(0, offset);
6431
+ const line = before.split("\n").length;
6432
+ return { line, column: offset - before.lastIndexOf("\n") };
6433
+ }
5926
6434
 
5927
- // src/lib/roblox.ts
5928
- var import_node_fs2 = require("fs");
5929
- var import_node_url2 = require("url");
5930
- var robloxDefsPath = (0, import_node_url2.fileURLToPath)(new URL("./roblox.d.luaut", importMetaUrl));
5931
- var robloxDefs = (0, import_node_fs2.readFileSync)(robloxDefsPath, "utf8");
5932
- var robloxLib = parse(robloxDefs);
6435
+ // src/project/libraries.ts
6436
+ var import_node_path2 = require("path");
6437
+ function resolveTypeLibraries(config, host = nodeHost) {
6438
+ const files = [];
6439
+ const problems = [];
6440
+ const loaded = /* @__PURE__ */ new Set();
6441
+ const addFile = (file) => {
6442
+ const key = pathKey(file);
6443
+ if (loaded.has(key)) return;
6444
+ loaded.add(key);
6445
+ files.push(file);
6446
+ };
6447
+ const addPackage = (directory, entryFile, visiting) => {
6448
+ const key = pathKey(directory);
6449
+ if (visiting.has(key)) return;
6450
+ visiting.add(key);
6451
+ for (const dependency of dependencyNames(directory, host)) {
6452
+ const found = findPackage(dependency, directory, host);
6453
+ if (found) addPackage(found.directory, found.file, visiting);
6454
+ }
6455
+ addFile(entryFile);
6456
+ };
6457
+ for (const entry of config.types) {
6458
+ const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
6459
+ if (relative) {
6460
+ const target = (0, import_node_path2.resolve)(config.directory, entry);
6461
+ if (entry.endsWith(".luaut")) {
6462
+ if (host.readFile(target) !== void 0) addFile(target);
6463
+ else problems.push({ file: config.path, message: `Cannot find type library file '${entry}'`, ...entryPosition(config, entry) });
6464
+ continue;
6465
+ }
6466
+ const file = packageEntry(target, host);
6467
+ if (file) addPackage(target, file, /* @__PURE__ */ new Set());
6468
+ else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
6469
+ continue;
6470
+ }
6471
+ const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
6472
+ const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
6473
+ if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
6474
+ else {
6475
+ problems.push({
6476
+ file: config.path,
6477
+ message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
6478
+ ...entryPosition(config, entry)
6479
+ });
6480
+ }
6481
+ }
6482
+ return { files, problems };
6483
+ }
6484
+ var ENTRY_FILE = "index.d.luaut";
6485
+ function packageEntry(directory, host) {
6486
+ const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
6487
+ const declared = manifest?.luaut?.types;
6488
+ const file = (0, import_node_path2.resolve)(directory, typeof declared === "string" ? declared : ENTRY_FILE);
6489
+ return host.readFile(file) !== void 0 ? file : void 0;
6490
+ }
6491
+ function findPackage(name, from, host) {
6492
+ let directory = (0, import_node_path2.resolve)(from);
6493
+ for (; ; ) {
6494
+ const candidate = (0, import_node_path2.join)(directory, "node_modules", ...name.split("/"));
6495
+ const file = packageEntry(candidate, host);
6496
+ if (file) return { directory: candidate, file };
6497
+ const parent = (0, import_node_path2.dirname)(directory);
6498
+ if (parent === directory) return void 0;
6499
+ directory = parent;
6500
+ }
6501
+ }
6502
+ function dependencyNames(directory, host) {
6503
+ const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
6504
+ const names = /* @__PURE__ */ new Set();
6505
+ for (const field of ["dependencies", "peerDependencies"]) {
6506
+ const deps = manifest?.[field];
6507
+ if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
6508
+ }
6509
+ return [...names];
6510
+ }
6511
+ function readJson(path, host) {
6512
+ const text = host.readFile(path);
6513
+ if (text === void 0) return void 0;
6514
+ try {
6515
+ const value = JSON.parse(text);
6516
+ return value && typeof value === "object" ? value : void 0;
6517
+ } catch {
6518
+ return void 0;
6519
+ }
6520
+ }
6521
+ function entryPosition(config, entry) {
6522
+ return keyPosition(config.source, entry);
6523
+ }
6524
+ function pathKey(path) {
6525
+ const normalized = (0, import_node_path2.resolve)(path);
6526
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
6527
+ }
6528
+
6529
+ // src/project/modules.ts
6530
+ var import_node_path3 = require("path");
6531
+ function moduleCandidates(fromFile, specifier, config) {
6532
+ const bases = specifier.startsWith("./") || specifier.startsWith("../") ? [(0, import_node_path3.resolve)((0, import_node_path3.dirname)(fromFile), specifier)] : config ? aliasTargets(config, specifier) : [];
6533
+ return bases.flatMap((base) => base.endsWith(".luaut") ? [base] : [`${base}.luaut`, `${base}.d.luaut`, (0, import_node_path3.join)(base, "index.luaut")]);
6534
+ }
6535
+ function resolveModulePath(fromFile, specifier, config, host = nodeHost) {
6536
+ return moduleCandidates(fromFile, specifier, config).find((path) => host.readFile(path) !== void 0);
6537
+ }
6538
+ function aliasTargets(config, specifier) {
6539
+ let match;
6540
+ let prefixLength = -1;
6541
+ for (const pattern2 of Object.keys(config.paths)) {
6542
+ const star = pattern2.indexOf("*");
6543
+ if (star < 0) {
6544
+ if (pattern2 === specifier) {
6545
+ match = { pattern: pattern2, wildcard: "" };
6546
+ break;
6547
+ }
6548
+ continue;
6549
+ }
6550
+ const prefix = pattern2.slice(0, star);
6551
+ const suffix = pattern2.slice(star + 1);
6552
+ const fits = specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix);
6553
+ if (fits && prefix.length > prefixLength) {
6554
+ prefixLength = prefix.length;
6555
+ match = { pattern: pattern2, wildcard: specifier.slice(prefix.length, specifier.length - suffix.length) };
6556
+ }
6557
+ }
6558
+ if (!match) return [];
6559
+ const { pattern, wildcard } = match;
6560
+ return config.paths[pattern].map((target) => (0, import_node_path3.resolve)(config.baseUrl, target.replace("*", wildcard)));
6561
+ }
6562
+
6563
+ // src/project/sourcemap.ts
6564
+ var import_node_path4 = require("path");
6565
+ var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
6566
+ var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
6567
+ var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".luaut", ".luau", ".lua"]);
6568
+ function sourceMapTypes(text, path, options) {
6569
+ let root;
6570
+ try {
6571
+ root = JSON.parse(text);
6572
+ } catch (error) {
6573
+ return { problem: `Invalid sourcemap: ${error.message}` };
6574
+ }
6575
+ if (!isNode(root)) return { problem: "Invalid sourcemap: the root must be an object with 'name' and 'className'" };
6576
+ const directory = (0, import_node_path4.dirname)((0, import_node_path4.resolve)(path));
6577
+ const lines = [];
6578
+ const aliasOfFile = /* @__PURE__ */ new Map();
6579
+ const used = /* @__PURE__ */ new Set();
6580
+ const aliasOfNode = /* @__PURE__ */ new Map();
6581
+ const aliasFor = (segments) => {
6582
+ const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
6583
+ let alias = base;
6584
+ for (let n = 2; used.has(alias); n++) alias = `${base}_${n}`;
6585
+ used.add(alias);
6586
+ return alias;
6587
+ };
6588
+ const visit = (node, segments, parent) => {
6589
+ const alias = aliasFor(segments);
6590
+ aliasOfNode.set(node, alias);
6591
+ for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey((0, import_node_path4.resolve)(directory, filePath)), alias);
6592
+ const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
6593
+ const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
6594
+ const members = [];
6595
+ if (parent) members.push(`Parent: ${parent}`);
6596
+ const named = /* @__PURE__ */ new Set();
6597
+ for (const child of node.children ?? []) {
6598
+ if (!isNode(child)) continue;
6599
+ const childAlias = visit(child, [...segments, child.name], alias);
6600
+ if (!IDENTIFIER.test(child.name) || taken.has(child.name) || named.has(child.name)) continue;
6601
+ named.add(child.name);
6602
+ members.push(`${child.name}: ${childAlias}`);
6603
+ }
6604
+ lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
6605
+ return alias;
6606
+ };
6607
+ const rootAlias = visit(root, [root.name], void 0);
6608
+ if (root.className === "DataModel") {
6609
+ lines.push(`declare game: ${rootAlias}`);
6610
+ const workspace = (root.children ?? []).find((child) => isNode(child) && child.className === "Workspace");
6611
+ const workspaceAlias = workspace && aliasOfNode.get(workspace);
6612
+ if (workspaceAlias) lines.push(`declare workspace: ${workspaceAlias}`);
6613
+ }
6614
+ let program;
6615
+ try {
6616
+ program = parse(lines.join("\n"));
6617
+ } catch (error) {
6618
+ return { problem: `Could not turn the sourcemap into types: ${error.message}` };
6619
+ }
6620
+ return {
6621
+ types: {
6622
+ program,
6623
+ scriptFor(file) {
6624
+ const alias = aliasOfFile.get(fileKey(file));
6625
+ return alias ? parse(`declare script: ${alias}`) : void 0;
6626
+ }
6627
+ }
6628
+ };
6629
+ }
6630
+ function isNode(value) {
6631
+ if (!value || typeof value !== "object") return false;
6632
+ const node = value;
6633
+ return typeof node.name === "string" && typeof node.className === "string";
6634
+ }
6635
+ function fileKey(path) {
6636
+ const extension = (0, import_node_path4.extname)(path);
6637
+ const bare = SCRIPT_EXTENSIONS.has(extension) ? path.slice(0, -extension.length) : path;
6638
+ const normalized = (0, import_node_path4.resolve)(bare);
6639
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
6640
+ }
5933
6641
 
5934
6642
  // src/index.ts
5935
- var defaultLibs = [luauLib, robloxLib];
5936
6643
  var luautparser = {
5937
6644
  tokenize,
5938
6645
  parseTokens,
@@ -5949,6 +6656,7 @@ var index_default = luautparser;
5949
6656
  // Annotate the CommonJS export names for ESM import in node:
5950
6657
  0 && (module.exports = {
5951
6658
  BinaryOperators,
6659
+ CONFIG_FILE_NAMES,
5952
6660
  Keywords,
5953
6661
  LexError,
5954
6662
  Operators,
@@ -5962,25 +6670,25 @@ var index_default = luautparser;
5962
6670
  booleanType,
5963
6671
  bufferType,
5964
6672
  containsTypeParam,
5965
- defaultLibs,
5966
6673
  difference,
5967
6674
  equalTypes,
5968
6675
  falsyType,
6676
+ findConfig,
5969
6677
  fn,
5970
6678
  formatType,
5971
6679
  getBinding,
5972
6680
  intersection,
5973
6681
  isAssignable,
6682
+ isClassType,
5974
6683
  isGlobal,
5975
6684
  isPossiblyFalsy,
5976
6685
  isPossiblyTruthy,
5977
6686
  isUnassignedGlobal,
5978
6687
  literal,
5979
- luauDefs,
5980
- luauDefsPath,
5981
- luauLib,
6688
+ loadConfig,
5982
6689
  luautparser,
5983
6690
  matchInfer,
6691
+ moduleCandidates,
5984
6692
  moduleExports,
5985
6693
  narrowExclude,
5986
6694
  narrowFalsy,
@@ -5988,6 +6696,7 @@ var index_default = luautparser;
5988
6696
  narrowTruthy,
5989
6697
  neverType,
5990
6698
  nilType,
6699
+ nodeHost,
5991
6700
  numberType,
5992
6701
  objectType,
5993
6702
  optional,
@@ -5997,11 +6706,12 @@ var index_default = luautparser;
5997
6706
  parseTokens,
5998
6707
  parseWithRecovery,
5999
6708
  primitive,
6000
- robloxDefs,
6001
- robloxDefsPath,
6002
- robloxLib,
6709
+ resolveModulePath,
6710
+ resolveTypeLibraries,
6003
6711
  setAliasExpander,
6712
+ sourceMapTypes,
6004
6713
  stringType,
6714
+ stripJsonComments,
6005
6715
  substitute,
6006
6716
  templateMatches,
6007
6717
  threadType,