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.js CHANGED
@@ -852,6 +852,9 @@ var Parser = class {
852
852
  }
853
853
  if (t.type === "Identifier" && t.value === "declare") {
854
854
  const p1 = this.peek(1);
855
+ if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
856
+ return this.parseDeclareClassStatement();
857
+ }
855
858
  if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
856
859
  return this.parseDeclareStatement();
857
860
  }
@@ -893,6 +896,36 @@ var Parser = class {
893
896
  const valueType = this.parseType();
894
897
  return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
895
898
  }
899
+ /** A declared type's name. It may be qualified once — `Enum.Material` —
900
+ * which is how a definitions file names types under a namespace, and how
901
+ * they are then written (`const m: Enum.Material`). */
902
+ parseTypeName() {
903
+ const first = this.expectIdentifier();
904
+ if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
905
+ this.advance();
906
+ const second = this.expectIdentifier();
907
+ return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
908
+ }
909
+ return tokenIdentifier(first);
910
+ }
911
+ // `declare class Name extends Base { member: T, ... }`
912
+ parseDeclareClassStatement() {
913
+ const start = this.current();
914
+ this.advance();
915
+ this.advance();
916
+ const name = this.parseTypeName();
917
+ let superclass;
918
+ if (this.checkIdentifierValue("extends")) {
919
+ this.advance();
920
+ const base = this.parseType();
921
+ if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
922
+ superclass = base;
923
+ }
924
+ if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
925
+ const body = this.parseTableType();
926
+ if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
927
+ return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
928
+ }
896
929
  // `import { a, b as c } from '...'` / `import Default from '...'` /
897
930
  // `import Default, { a } from '...'`. Compiled away entirely by the
898
931
  // bundler — never survives into emitted Luau.
@@ -1223,8 +1256,7 @@ var Parser = class {
1223
1256
  parseTypeAliasStatement() {
1224
1257
  const start = this.current();
1225
1258
  this.advance();
1226
- const nameTok = this.expectIdentifier();
1227
- const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
1259
+ const name = this.parseTypeName();
1228
1260
  let generics = [];
1229
1261
  if (this.checkOperator("<")) {
1230
1262
  generics = this.parseGenericTypeParameterList();
@@ -2726,6 +2758,9 @@ var Analyzer = class {
2726
2758
  case "DeclareStatement":
2727
2759
  this.visitType(stmt.valueType, scope);
2728
2760
  return;
2761
+ case "DeclareClassStatement":
2762
+ this.visitType(stmt.body, scope);
2763
+ return;
2729
2764
  case "TypeAliasStatement":
2730
2765
  case "ExportTypeAliasStatement":
2731
2766
  this.visitType(stmt.definition, scope);
@@ -2899,6 +2934,9 @@ function analyzeScopes(program, options = {}) {
2899
2934
  }
2900
2935
 
2901
2936
  // src/ast/typeModel.ts
2937
+ function isClassType(t) {
2938
+ return t.kind === "object" && t.class !== void 0;
2939
+ }
2902
2940
  function typeParam(name, constraint, isConst) {
2903
2941
  return { kind: "typeParam", name, constraint, isConst };
2904
2942
  }
@@ -2958,10 +2996,16 @@ function substitute(t, subst) {
2958
2996
  }
2959
2997
  case "function": {
2960
2998
  const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
2999
+ let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
3000
+ let varargs = t.varargs && substitute(t.varargs, inner);
3001
+ if (varargs?.kind === "tuple" && varargs.isPack) {
3002
+ params = [...params, ...varargs.elements.map((type) => ({ type }))];
3003
+ varargs = void 0;
3004
+ }
2961
3005
  return {
2962
3006
  kind: "function",
2963
- params: t.params.map((p) => ({ ...p, type: substitute(p.type, inner) })),
2964
- varargs: t.varargs && substitute(t.varargs, inner),
3007
+ params,
3008
+ varargs,
2965
3009
  returns: substitute(t.returns, inner),
2966
3010
  typeParams: t.typeParams,
2967
3011
  predicate: t.predicate && {
@@ -3041,7 +3085,8 @@ function unify(param, arg, vars, out) {
3041
3085
  }
3042
3086
  return;
3043
3087
  case "object":
3044
- if (arg.kind === "object") {
3088
+ if (param.class) return;
3089
+ if (arg.kind === "object" && !arg.class) {
3045
3090
  for (const [k, pv] of param.properties) {
3046
3091
  const av = arg.properties.get(k);
3047
3092
  if (av) unify(pv.type, av.type, vars, out);
@@ -3124,7 +3169,7 @@ function widen(t) {
3124
3169
  case "tuple":
3125
3170
  return tuple(t.elements.map(widen), t.isPack);
3126
3171
  case "object": {
3127
- if (t.frozen) return t;
3172
+ if (t.frozen || t.class) return t;
3128
3173
  const entries = [];
3129
3174
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
3130
3175
  const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
@@ -3151,6 +3196,10 @@ function isAssignable(rawA, rawB) {
3151
3196
  if (expandAlias) {
3152
3197
  if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
3153
3198
  else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
3199
+ else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
3200
+ a = expandAlias(a);
3201
+ b = expandAlias(b);
3202
+ }
3154
3203
  if (a === b) return true;
3155
3204
  }
3156
3205
  for (let i = 0; i < comparing.length; i += 2) {
@@ -3176,7 +3225,11 @@ function isAssignableInner(a, b) {
3176
3225
  if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
3177
3226
  if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
3178
3227
  if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
3179
- if (a.kind === "intersection") return a.types.some((t) => isAssignable(t, b));
3228
+ if (a.kind === "intersection") {
3229
+ if (a.types.some((t) => isAssignable(t, b))) return true;
3230
+ const merged = mergeObjectMembers(a.types);
3231
+ return merged !== void 0 && isAssignable(merged, b);
3232
+ }
3180
3233
  if (a.kind === "literal") {
3181
3234
  if (b.kind === "literal") return a.value === b.value;
3182
3235
  if (b.kind === "primitive") return b.name === a.base;
@@ -3202,6 +3255,8 @@ function isAssignableInner(a, b) {
3202
3255
  }
3203
3256
  if (a.kind === "object") {
3204
3257
  if (b.kind !== "object") return false;
3258
+ if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
3259
+ if (a.class && (b.indexer || b.properties.size === 0)) return false;
3205
3260
  for (const [name, bp] of b.properties) {
3206
3261
  const ap = a.properties.get(name);
3207
3262
  if (!ap) {
@@ -3344,6 +3399,7 @@ function containsFreeTypeParam(t, seen, bound) {
3344
3399
  case "intersection":
3345
3400
  return t.types.some((m) => containsTypeParam(m, seen, bound));
3346
3401
  case "object":
3402
+ if (t.class) return false;
3347
3403
  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));
3348
3404
  case "function": {
3349
3405
  const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
@@ -3531,6 +3587,39 @@ var IDENT_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
3531
3587
  function formatKey(k) {
3532
3588
  return IDENT_KEY.test(k) ? k : JSON.stringify(k);
3533
3589
  }
3590
+ function mergeObjectMembers(types) {
3591
+ const objects = [];
3592
+ const seen = /* @__PURE__ */ new Set();
3593
+ const collect = (t) => {
3594
+ if (seen.has(t)) return true;
3595
+ seen.add(t);
3596
+ if (t.kind === "genericRef") {
3597
+ const expanded = expandAlias?.(t);
3598
+ return expanded !== void 0 && expanded !== t && collect(expanded);
3599
+ }
3600
+ if (t.kind === "intersection") return t.types.every(collect);
3601
+ if (t.kind === "object" && !t.class) {
3602
+ objects.push(t);
3603
+ return true;
3604
+ }
3605
+ return false;
3606
+ };
3607
+ if (!types.every(collect) || objects.length < 2) return void 0;
3608
+ const properties = /* @__PURE__ */ new Map();
3609
+ let indexer;
3610
+ for (const object of objects) {
3611
+ indexer ??= object.indexer;
3612
+ for (const [name, property] of object.properties) {
3613
+ const existing = properties.get(name);
3614
+ properties.set(name, existing ? {
3615
+ type: intersection([existing.type, property.type]),
3616
+ optional: existing.optional && property.optional,
3617
+ readonly: existing.readonly || property.readonly
3618
+ } : property);
3619
+ }
3620
+ }
3621
+ return objectType([...properties], indexer);
3622
+ }
3534
3623
 
3535
3624
  // src/ast/analyzeTypes.ts
3536
3625
  function analyzeTypes(program, scopes, options = {}) {
@@ -3721,6 +3810,62 @@ function keepsLiterals(paramType) {
3721
3810
  const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
3722
3811
  return members.some((m) => m.kind === "literal");
3723
3812
  }
3813
+ var AliasMap = class extends Map {
3814
+ pending = /* @__PURE__ */ new Map();
3815
+ defer(name, resolve5) {
3816
+ super.delete(name);
3817
+ this.pending.set(name, resolve5);
3818
+ }
3819
+ get(name) {
3820
+ const resolved = super.get(name);
3821
+ if (resolved !== void 0) return resolved;
3822
+ const resolve5 = this.pending.get(name);
3823
+ if (!resolve5) return void 0;
3824
+ this.pending.delete(name);
3825
+ const type = resolve5();
3826
+ super.set(name, type);
3827
+ return type;
3828
+ }
3829
+ has(name) {
3830
+ return super.has(name) || (this.pending?.has(name) ?? false);
3831
+ }
3832
+ set(name, type) {
3833
+ this.pending?.delete(name);
3834
+ return super.set(name, type);
3835
+ }
3836
+ delete(name) {
3837
+ const deferred = this.pending?.delete(name) ?? false;
3838
+ return super.delete(name) || deferred;
3839
+ }
3840
+ get size() {
3841
+ return super.size + (this.pending?.size ?? 0);
3842
+ }
3843
+ keys() {
3844
+ return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
3845
+ }
3846
+ entries() {
3847
+ return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
3848
+ }
3849
+ values() {
3850
+ return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
3851
+ }
3852
+ forEach(callback, thisArg) {
3853
+ for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
3854
+ }
3855
+ [Symbol.iterator]() {
3856
+ return this.entries();
3857
+ }
3858
+ };
3859
+ var METAMETHODS = {
3860
+ "+": "__add",
3861
+ "-": "__sub",
3862
+ "*": "__mul",
3863
+ "/": "__div",
3864
+ "//": "__idiv",
3865
+ "%": "__mod",
3866
+ "^": "__pow",
3867
+ "..": "__concat"
3868
+ };
3724
3869
  function posKey(name, line, column) {
3725
3870
  return `${name}@${line}:${column}`;
3726
3871
  }
@@ -3741,9 +3886,12 @@ var TypeAnalyzer = class {
3741
3886
  expectedTypeOf = /* @__PURE__ */ new Map();
3742
3887
  /** Public: each alias resolved once (generic aliases keep their params as
3743
3888
  * `typeParam` nodes in the body). */
3744
- aliases = /* @__PURE__ */ new Map();
3889
+ aliases = new AliasMap();
3745
3890
  /** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
3746
3891
  aliasDefs = /* @__PURE__ */ new Map();
3892
+ /** See `resolveClass`. */
3893
+ classTypes = /* @__PURE__ */ new WeakMap();
3894
+ classMembers = /* @__PURE__ */ new WeakMap();
3747
3895
  /** Generic parameters currently in lexical scope (alias body / generic fn),
3748
3896
  * with their `extends` constraints resolved. */
3749
3897
  typeParamScope = [];
@@ -3853,24 +4001,123 @@ var TypeAnalyzer = class {
3853
4001
  for (const stmt of block.statements) {
3854
4002
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
3855
4003
  if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
4004
+ if (stmt.type === "DeclareClassStatement") {
4005
+ this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
4006
+ }
3856
4007
  }
3857
4008
  }
3858
- /** Seed global types from `declare` statements. Repeating a name builds an
3859
- * *overload set* (an intersection, in declaration order) rather than
3860
- * replacing which is how `typeof` gets one signature per result string. */
4009
+ /** A non-generic definition's type. */
4010
+ resolveDef(def) {
4011
+ return def.class ? this.classType(def.class) : this.resolveType(def.node);
4012
+ }
4013
+ /** One type per class declaration, so every mention of a class is the same
4014
+ * object — its own members included, which refer back to it. */
4015
+ classType(stmt) {
4016
+ return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
4017
+ }
4018
+ /** A class's members are resolved the first time anyone asks for
4019
+ * `properties` — its own from its body, the inherited ones from its
4020
+ * superclass.
4021
+ *
4022
+ * Both have to wait. A definitions file for a whole engine declares
4023
+ * thousands of classes that all refer to one another; resolving each body
4024
+ * as soon as the class is named would resolve every class on every
4025
+ * analysis, when a script touches a handful. And classes refer to one
4026
+ * another constantly — `Object.IsA` mentions a map of every class, each
4027
+ * of which extends `Object` — so while one class resolves, one it extends
4028
+ * may itself be half-resolved; copying its members then would miss some
4029
+ * for good. */
4030
+ resolveClass(stmt) {
4031
+ const name = stmt.name.name;
4032
+ const { ancestors, cyclic } = this.classChain(stmt);
4033
+ const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
4034
+ let own;
4035
+ let resolvingOwn = false;
4036
+ const ownMembers = () => {
4037
+ if (own || resolvingOwn) return own;
4038
+ resolvingOwn = true;
4039
+ try {
4040
+ own = this.resolveType(stmt.body);
4041
+ } finally {
4042
+ resolvingOwn = false;
4043
+ }
4044
+ return own;
4045
+ };
4046
+ let complete;
4047
+ const members = () => {
4048
+ if (complete) return complete;
4049
+ const mine = ownMembers();
4050
+ if (!mine) return void 0;
4051
+ const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
4052
+ if (superclass && !base) return void 0;
4053
+ return complete = {
4054
+ properties: new Map([...base?.properties ?? [], ...mine.properties]),
4055
+ indexer: mine.indexer ?? base?.indexer
4056
+ };
4057
+ };
4058
+ const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
4059
+ Object.defineProperties(type, {
4060
+ properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
4061
+ indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
4062
+ });
4063
+ this.classTypes.set(stmt, type);
4064
+ this.classMembers.set(type, members);
4065
+ if (this.program.body.statements.includes(stmt)) ownMembers();
4066
+ return type;
4067
+ }
4068
+ /** `extends` must name a class, and the chain must end. */
4069
+ checkClass(stmt) {
4070
+ if (!stmt.superclass || !this.emitDiagnostics) return;
4071
+ const base = stmt.superclass.base;
4072
+ if (!this.aliasDefs.get(base)?.class) {
4073
+ const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
4074
+ this.diagnostics.push({
4075
+ node: stmt.superclass,
4076
+ message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
4077
+ });
4078
+ } else if (this.classChain(stmt).cyclic) {
4079
+ this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
4080
+ }
4081
+ }
4082
+ /** The class and the classes it extends, nearest first, read from the
4083
+ * declarations — no type has to be resolved to know them. The walk stops
4084
+ * at a superclass that is not a class. */
4085
+ classChain(stmt) {
4086
+ const ancestors = [stmt.name.name];
4087
+ for (let cls = stmt; cls?.superclass; ) {
4088
+ const base = cls.superclass.base;
4089
+ if (ancestors.includes(base)) return { ancestors, cyclic: true };
4090
+ cls = this.aliasDefs.get(base)?.class;
4091
+ if (!cls) break;
4092
+ ancestors.push(base);
4093
+ }
4094
+ return { ancestors, cyclic: false };
4095
+ }
4096
+ /** Seed global types from `declare` statements. Repeating a function name
4097
+ * builds an *overload set* (an intersection, in declaration order) rather
4098
+ * than replacing — which is how `typeof` gets one signature per result
4099
+ * string. Any other value is simply redeclared: a sourcemap's
4100
+ * `declare script: <this file's instance>` replaces the library's
4101
+ * `declare script: LuaSourceContainer`. */
3861
4102
  harvestDeclares(block) {
3862
4103
  for (const stmt of block.statements) {
3863
4104
  if (stmt.type !== "DeclareStatement") continue;
3864
4105
  const t = this.resolveType(stmt.valueType);
3865
4106
  const prev = this.libGlobalTypes.get(stmt.name);
3866
- this.libGlobalTypes.set(stmt.name, prev ? intersection([prev, t]) : t);
4107
+ const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
4108
+ this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
3867
4109
  }
3868
4110
  }
3869
4111
  resolveAllAliases() {
3870
4112
  for (const [name, def] of this.aliasDefs) {
4113
+ if (def.class && !this.program.body.statements.includes(def.class)) {
4114
+ const cls = def.class;
4115
+ this.aliases.defer(name, () => this.classType(cls));
4116
+ continue;
4117
+ }
3871
4118
  if (containsTypeQuery(def.node)) continue;
3872
4119
  this.withTypeParams(def.params, () => {
3873
- this.aliases.set(name, this.resolveType(def.node));
4120
+ this.aliases.set(name, this.resolveDef(def));
3874
4121
  });
3875
4122
  }
3876
4123
  }
@@ -3880,7 +4127,7 @@ var TypeAnalyzer = class {
3880
4127
  for (const [name, def] of this.aliasDefs) {
3881
4128
  if (this.aliases.has(name)) continue;
3882
4129
  this.withTypeParams(def.params, () => {
3883
- this.aliases.set(name, this.resolveType(def.node));
4130
+ this.aliases.set(name, this.resolveDef(def));
3884
4131
  });
3885
4132
  }
3886
4133
  return this.aliases;
@@ -3908,10 +4155,7 @@ var TypeAnalyzer = class {
3908
4155
  /** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
3909
4156
  instantiateAlias(def, args) {
3910
4157
  if (this.instantiationDepth > 20) return unknownType;
3911
- const subst = /* @__PURE__ */ new Map();
3912
- def.params.forEach((p, i) => {
3913
- subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
3914
- });
4158
+ const subst = this.bindTypeArguments(def.params, args);
3915
4159
  this.instantiationDepth++;
3916
4160
  try {
3917
4161
  const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
@@ -3920,6 +4164,24 @@ var TypeAnalyzer = class {
3920
4164
  this.instantiationDepth--;
3921
4165
  }
3922
4166
  }
4167
+ /** Pair written type arguments with the parameters they instantiate. A
4168
+ * pack parameter (`T...`) takes every argument from its position on, as
4169
+ * one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
4170
+ * and `Signal<()>` to the empty pack. Left out, a parameter takes its
4171
+ * default (`T... = ...any` is `any`), or `unknown`. */
4172
+ bindTypeArguments(params, args) {
4173
+ const subst = /* @__PURE__ */ new Map();
4174
+ params.forEach((p, i) => {
4175
+ let arg = args[i];
4176
+ if (p.isPack && i < args.length) {
4177
+ const rest = args.slice(i);
4178
+ const single = rest.length === 1 ? rest[0] : void 0;
4179
+ arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
4180
+ }
4181
+ subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
4182
+ });
4183
+ return subst;
4184
+ }
3923
4185
  // --------------------------------------------------------
3924
4186
  // TypeNode -> Type
3925
4187
  // --------------------------------------------------------
@@ -3981,6 +4243,12 @@ var TypeAnalyzer = class {
3981
4243
  }
3982
4244
  const lib = this.options.libTypes?.[node.base];
3983
4245
  if (lib) return lib;
4246
+ } else if (this.aliasDefs.has(name)) {
4247
+ return this.expand({
4248
+ kind: "genericRef",
4249
+ name,
4250
+ typeArguments: node.typeArguments.map((a) => this.resolveType(a))
4251
+ });
3984
4252
  }
3985
4253
  return {
3986
4254
  kind: "genericRef",
@@ -4107,6 +4375,7 @@ var TypeAnalyzer = class {
4107
4375
  return this.resolveType(node.typeAnnotation);
4108
4376
  case "TypePackNode": {
4109
4377
  if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
4378
+ if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
4110
4379
  return tuple(node.types.map((t) => this.resolveType(t)), true);
4111
4380
  }
4112
4381
  }
@@ -4130,7 +4399,7 @@ var TypeAnalyzer = class {
4130
4399
  this.reduceDepth++;
4131
4400
  try {
4132
4401
  const result = this.reduceTypeInner(t);
4133
- this.reduceCache.set(t, result);
4402
+ if (result.kind !== "keyof") this.reduceCache.set(t, result);
4134
4403
  return result;
4135
4404
  } finally {
4136
4405
  this.reduceDepth--;
@@ -4142,6 +4411,7 @@ var TypeAnalyzer = class {
4142
4411
  case "keyof": {
4143
4412
  const target = this.reduceType(t.target);
4144
4413
  if (containsTypeParam(target)) return { kind: "keyof", target };
4414
+ if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
4145
4415
  return this.keysOf(target);
4146
4416
  }
4147
4417
  case "indexedAccess": {
@@ -4183,6 +4453,7 @@ var TypeAnalyzer = class {
4183
4453
  t.predicate
4184
4454
  );
4185
4455
  case "object": {
4456
+ if (t.class) return t;
4186
4457
  const entries = [];
4187
4458
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
4188
4459
  const reduced = objectType(entries, t.indexer && {
@@ -4322,6 +4593,7 @@ var TypeAnalyzer = class {
4322
4593
  t.typeParams
4323
4594
  );
4324
4595
  case "object": {
4596
+ if (t.class) return t;
4325
4597
  const entries = [];
4326
4598
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
4327
4599
  return objectType(entries, t.indexer && {
@@ -4375,6 +4647,11 @@ var TypeAnalyzer = class {
4375
4647
  visitStatement(stmt, env) {
4376
4648
  switch (stmt.type) {
4377
4649
  case "VariableDeclaration": {
4650
+ stmt.names.forEach((target, i) => {
4651
+ if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
4652
+ this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
4653
+ }
4654
+ });
4378
4655
  const { types: valueTypes, sources } = this.valueList(stmt.init, env);
4379
4656
  stmt.names.forEach((target, i) => {
4380
4657
  const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
@@ -4435,6 +4712,16 @@ var TypeAnalyzer = class {
4435
4712
  return;
4436
4713
  }
4437
4714
  case "AssignmentStatement": {
4715
+ stmt.targets.forEach((target, i) => {
4716
+ const value = stmt.values[i];
4717
+ if (!value) return;
4718
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
4719
+ this.applyContext(value, this.infer(target, env));
4720
+ } else if (target.type === "Identifier") {
4721
+ const id = this.bindingIdOf(target);
4722
+ if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
4723
+ }
4724
+ });
4438
4725
  const { types: valueTypes, sources } = this.valueList(stmt.values, env);
4439
4726
  stmt.targets.forEach((target, i) => {
4440
4727
  const vt = valueTypes[i] ?? unknownType;
@@ -4579,6 +4866,9 @@ var TypeAnalyzer = class {
4579
4866
  case "BreakStatement":
4580
4867
  this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
4581
4868
  return;
4869
+ case "DeclareClassStatement":
4870
+ this.checkClass(stmt);
4871
+ return;
4582
4872
  case "ContinueStatement":
4583
4873
  case "TypeAliasStatement":
4584
4874
  case "ExportTypeAliasStatement":
@@ -4700,7 +4990,46 @@ var TypeAnalyzer = class {
4700
4990
  }
4701
4991
  if (p.pattern) return this.patternToType(p.pattern, env);
4702
4992
  if (p.default) return widen(this.infer(p.default, env));
4703
- return anyType;
4993
+ return this.contextualParams.get(p) ?? anyType;
4994
+ }
4995
+ /** What a function expression's unannotated parameters are, from where
4996
+ * it is written — see `applyContext`. */
4997
+ contextualParams = /* @__PURE__ */ new WeakMap();
4998
+ /** `expected` is the type the surroundings want for `expr`. A function
4999
+ * expression written there takes its unannotated parameters' types from
5000
+ * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
5001
+ * `player` from `Connect`'s callback type. Anything else is inferred as
5002
+ * usual. */
5003
+ applyContext(expr, expected) {
5004
+ let e = expr;
5005
+ while (e.type === "ParenthesizedExpression") e = e.expression;
5006
+ if (e.type !== "FunctionExpression" || !expected) return;
5007
+ const members = expected.kind === "union" ? expected.types : [expected];
5008
+ const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
5009
+ if (!signatures.length) return;
5010
+ e.func.params.forEach((p, k) => {
5011
+ if (p.typeAnnotation || p.pattern || p.default) return;
5012
+ const candidates = [];
5013
+ for (const signature of signatures) {
5014
+ const t2 = signature.params[k]?.type ?? signature.varargs;
5015
+ if (t2) candidates.push(t2);
5016
+ }
5017
+ if (!candidates.length) return;
5018
+ const t = union(candidates);
5019
+ this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
5020
+ });
5021
+ }
5022
+ /** The parameter type each written argument lands on, across `fns`. */
5023
+ expectedArguments(written, fns, selfOf) {
5024
+ return written.map((_, j) => {
5025
+ const candidates = [];
5026
+ for (const f of fns) {
5027
+ const i = j + selfOf(f);
5028
+ const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
5029
+ if (param) candidates.push(param);
5030
+ }
5031
+ return candidates.length ? union(candidates) : void 0;
5032
+ });
4704
5033
  }
4705
5034
  /** Synthesize a type from a destructuring pattern used without an
4706
5035
  * annotation (`function f({ a, b = 1 })`). */
@@ -4752,7 +5081,8 @@ var TypeAnalyzer = class {
4752
5081
  f.params.forEach((p, i) => {
4753
5082
  const arg = argTypes[i];
4754
5083
  if (arg === void 0) return;
4755
- unify(p.type, keepsLiterals(p.type) ? arg : widen(arg), vars, subst);
5084
+ const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
5085
+ unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
4756
5086
  });
4757
5087
  for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
4758
5088
  return subst;
@@ -4813,12 +5143,13 @@ var TypeAnalyzer = class {
4813
5143
  return;
4814
5144
  }
4815
5145
  const t = value;
5146
+ if (t.kind === "object" && t.class) return;
4816
5147
  if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
4817
5148
  bounds.set(t.name, this.reduceType(t.constraint));
4818
5149
  }
4819
5150
  for (const child of Object.values(value)) walk(child);
4820
5151
  };
4821
- for (const p of f.params) walk(p.type);
5152
+ for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
4822
5153
  return f.params.map((p) => substitute(p.type, bounds));
4823
5154
  }
4824
5155
  /** Record what each written argument is expected to be — see
@@ -5157,7 +5488,7 @@ var TypeAnalyzer = class {
5157
5488
  this.expandCache.set(key, t);
5158
5489
  this.resolvingAliases.add(t.name);
5159
5490
  try {
5160
- const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveType(def.node);
5491
+ const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
5161
5492
  const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
5162
5493
  this.expandCache.set(key, named);
5163
5494
  return named;
@@ -5280,9 +5611,9 @@ var TypeAnalyzer = class {
5280
5611
  case "not":
5281
5612
  return booleanType;
5282
5613
  case "-":
5283
- return numberType;
5614
+ return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
5284
5615
  case "#":
5285
- return numberType;
5616
+ return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
5286
5617
  }
5287
5618
  return arg;
5288
5619
  }
@@ -5304,7 +5635,7 @@ var TypeAnalyzer = class {
5304
5635
  const r = this.infer(expr.right, env);
5305
5636
  switch (op) {
5306
5637
  case "..":
5307
- return stringType;
5638
+ return this.operatorResult(expr, op, l, r) ?? stringType;
5308
5639
  case "==":
5309
5640
  case "~=":
5310
5641
  case "<":
@@ -5319,7 +5650,7 @@ var TypeAnalyzer = class {
5319
5650
  case "//":
5320
5651
  case "%":
5321
5652
  case "^":
5322
- return numberType;
5653
+ return this.operatorResult(expr, op, l, r) ?? numberType;
5323
5654
  }
5324
5655
  return union([l, r]);
5325
5656
  }
@@ -5338,8 +5669,10 @@ var TypeAnalyzer = class {
5338
5669
  }
5339
5670
  case "CallExpression": {
5340
5671
  const callee = this.infer(expr.callee, env);
5341
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5342
5672
  const fns = this.overloadsOf(callee);
5673
+ const expected = this.expectedArguments(expr.arguments, fns, () => 0);
5674
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5675
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
5343
5676
  if (fns.length) {
5344
5677
  this.recordExpected(expr.arguments, fns, () => 0);
5345
5678
  const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
@@ -5354,8 +5687,10 @@ var TypeAnalyzer = class {
5354
5687
  }
5355
5688
  case "MethodCallExpression": {
5356
5689
  const objType = this.infer(expr.object, env);
5357
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5358
5690
  const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
5691
+ const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
5692
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5693
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
5359
5694
  if (fns.length) {
5360
5695
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5361
5696
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -5747,6 +6082,35 @@ var TypeAnalyzer = class {
5747
6082
  this.selfType = saved;
5748
6083
  }
5749
6084
  }
6085
+ /** What an operator on a value with metamethods gives: `a + b` calls
6086
+ * `__add` on `a`, or failing that on `b` with the operands swapped — the
6087
+ * order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
6088
+ * Vector3` and `2 * vector` get their types from the declarations.
6089
+ * `undefined` when neither operand declares the metamethod; an operand
6090
+ * that declares it but accepts neither argument is reported. */
6091
+ operatorResult(node, op, left, right) {
6092
+ const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
6093
+ if (!name) return void 0;
6094
+ const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
6095
+ let declared;
6096
+ for (const [receiver, other] of candidates) {
6097
+ const t = this.expand(receiver);
6098
+ const method = t.kind === "object" ? t.properties.get(name) : void 0;
6099
+ if (!method) continue;
6100
+ declared ??= receiver;
6101
+ const args = other === void 0 ? [receiver] : [receiver, other];
6102
+ const picked = this.pickOverload(this.overloadsOf(method.type), args);
6103
+ if (picked) return this.callReturn(picked, args);
6104
+ }
6105
+ if (declared && this.emitDiagnostics) {
6106
+ this.diagnostics.push({
6107
+ node,
6108
+ 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)}'`
6109
+ });
6110
+ return anyType;
6111
+ }
6112
+ return void 0;
6113
+ }
5750
6114
  /** Does this signature take the receiver as its first parameter?
5751
6115
  *
5752
6116
  * Luau's `:` is sugar both ways: `function T:m(a)` declares
@@ -5823,22 +6187,366 @@ function briefType(t) {
5823
6187
  return formatType(t);
5824
6188
  }
5825
6189
 
5826
- // src/lib/luau.ts
5827
- import { readFileSync } from "fs";
5828
- import { fileURLToPath } from "url";
5829
- var luauDefsPath = fileURLToPath(new URL("./luau.d.luaut", import.meta.url));
5830
- var luauDefs = readFileSync(luauDefsPath, "utf8");
5831
- var luauLib = parse(luauDefs);
6190
+ // src/project/host.ts
6191
+ import { readFileSync, statSync } from "fs";
6192
+ var nodeHost = {
6193
+ readFile(path) {
6194
+ try {
6195
+ return statSync(path).isFile() ? readFileSync(path, "utf8") : void 0;
6196
+ } catch {
6197
+ return void 0;
6198
+ }
6199
+ }
6200
+ };
6201
+
6202
+ // src/project/config.ts
6203
+ import { dirname, join, resolve } from "path";
6204
+ var CONFIG_FILE_NAMES = ["luaut.config.json", "luaut.config.jsonc"];
6205
+ function findConfig(file, host = nodeHost) {
6206
+ const searched = [];
6207
+ let directory = dirname(resolve(file));
6208
+ for (; ; ) {
6209
+ const found = [];
6210
+ for (const name of CONFIG_FILE_NAMES) {
6211
+ const path = join(directory, name);
6212
+ searched.push(path);
6213
+ if (host.readFile(path) !== void 0) found.push(path);
6214
+ }
6215
+ if (found.length > 1) {
6216
+ const message = `Only one luaut config may be in a folder, but both ${CONFIG_FILE_NAMES.join(" and ")} are in ${directory}`;
6217
+ return { searched, problems: found.map((path) => ({ file: path, message, line: 1, column: 1 })) };
6218
+ }
6219
+ if (found.length === 1) {
6220
+ const { config, problems } = loadConfig(found[0], host);
6221
+ return { config, problems, searched };
6222
+ }
6223
+ const parent = dirname(directory);
6224
+ if (parent === directory) return { searched, problems: [] };
6225
+ directory = parent;
6226
+ }
6227
+ }
6228
+ var OPTIONS = ["types", "paths", "baseUrl", "sourceMap"];
6229
+ function loadConfig(path, host = nodeHost) {
6230
+ const file = resolve(path);
6231
+ const source = host.readFile(file);
6232
+ if (source === void 0) return { problems: [{ file, message: "Cannot read the config file" }] };
6233
+ let raw;
6234
+ try {
6235
+ raw = JSON.parse(stripJsonComments(source));
6236
+ } catch (error) {
6237
+ const message = error.message;
6238
+ return { problems: [{ file, message: `Invalid JSON: ${message}`, ...jsonErrorPosition(source, message) }] };
6239
+ }
6240
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
6241
+ return { problems: [{ file, message: "The config must be a JSON object", line: 1, column: 1 }] };
6242
+ }
6243
+ const directory = dirname(file);
6244
+ const options = raw;
6245
+ const problems = [];
6246
+ const at = (key) => keyPosition(source, key);
6247
+ const problem = (key, message) => {
6248
+ problems.push({ file, message, ...at(key) });
6249
+ };
6250
+ for (const key of Object.keys(options)) {
6251
+ if (!OPTIONS.includes(key)) {
6252
+ problem(key, `Unknown option '${key}'. Options are: ${OPTIONS.join(", ")}`);
6253
+ }
6254
+ }
6255
+ let types = [];
6256
+ if (options.types !== void 0) {
6257
+ if (Array.isArray(options.types) && options.types.every((t) => typeof t === "string")) types = options.types;
6258
+ else problem("types", `'types' must be an array of strings, such as ["luau"]`);
6259
+ }
6260
+ const paths = {};
6261
+ if (options.paths !== void 0) {
6262
+ const value = options.paths;
6263
+ if (value && typeof value === "object" && !Array.isArray(value)) {
6264
+ for (const [pattern, targets] of Object.entries(value)) {
6265
+ if (Array.isArray(targets) && targets.every((t) => typeof t === "string")) paths[pattern] = targets;
6266
+ else problem(pattern, `'paths' entry '${pattern}' must be an array of strings`);
6267
+ if (pattern.split("*").length > 2) problem(pattern, `'paths' pattern '${pattern}' may contain at most one '*'`);
6268
+ }
6269
+ } else {
6270
+ problem("paths", `'paths' must be an object, such as { "@shared/*": ["src/shared/*"] }`);
6271
+ }
6272
+ }
6273
+ let baseUrl = directory;
6274
+ if (options.baseUrl !== void 0) {
6275
+ if (typeof options.baseUrl === "string") baseUrl = resolve(directory, options.baseUrl);
6276
+ else problem("baseUrl", "'baseUrl' must be a string");
6277
+ }
6278
+ let sourceMap = null;
6279
+ if (options.sourceMap !== void 0 && options.sourceMap !== null) {
6280
+ if (typeof options.sourceMap === "string") sourceMap = resolve(directory, options.sourceMap);
6281
+ else problem("sourceMap", "'sourceMap' must be a path string, or null for none");
6282
+ }
6283
+ return { config: { path: file, directory, source, types, paths, baseUrl, sourceMap }, problems };
6284
+ }
6285
+ function stripJsonComments(text) {
6286
+ const out = text.split("");
6287
+ let i = 0;
6288
+ let inString = false;
6289
+ while (i < text.length) {
6290
+ const ch = text[i];
6291
+ if (inString) {
6292
+ if (ch === "\\") i += 2;
6293
+ else {
6294
+ if (ch === '"') inString = false;
6295
+ i++;
6296
+ }
6297
+ continue;
6298
+ }
6299
+ if (ch === '"') {
6300
+ inString = true;
6301
+ i++;
6302
+ } else if (ch === "/" && text[i + 1] === "/") {
6303
+ while (i < text.length && text[i] !== "\n") out[i++] = " ";
6304
+ } else if (ch === "/" && text[i + 1] === "*") {
6305
+ out[i++] = " ";
6306
+ out[i++] = " ";
6307
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
6308
+ if (text[i] !== "\n") out[i] = " ";
6309
+ i++;
6310
+ }
6311
+ if (i < text.length) {
6312
+ out[i++] = " ";
6313
+ out[i++] = " ";
6314
+ }
6315
+ } else if (ch === ",") {
6316
+ let j = i + 1;
6317
+ while (j < text.length && /\s/.test(text[j])) j++;
6318
+ if (text[j] === "}" || text[j] === "]") out[i] = " ";
6319
+ i++;
6320
+ } else {
6321
+ i++;
6322
+ }
6323
+ }
6324
+ return out.join("");
6325
+ }
6326
+ function jsonErrorPosition(source, message) {
6327
+ const lineColumn = /line (\d+) column (\d+)/.exec(message);
6328
+ if (lineColumn) return { line: Number(lineColumn[1]), column: Number(lineColumn[2]) };
6329
+ const position = /position (\d+)/.exec(message);
6330
+ return position ? offsetPosition(source, Number(position[1])) : { line: 1, column: 1 };
6331
+ }
6332
+ function keyPosition(source, key) {
6333
+ const offset = source.indexOf(JSON.stringify(key));
6334
+ return offset < 0 ? { line: 1, column: 1 } : offsetPosition(source, offset);
6335
+ }
6336
+ function offsetPosition(source, offset) {
6337
+ const before = source.slice(0, offset);
6338
+ const line = before.split("\n").length;
6339
+ return { line, column: offset - before.lastIndexOf("\n") };
6340
+ }
6341
+
6342
+ // src/project/libraries.ts
6343
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
6344
+ function resolveTypeLibraries(config, host = nodeHost) {
6345
+ const files = [];
6346
+ const problems = [];
6347
+ const loaded = /* @__PURE__ */ new Set();
6348
+ const addFile = (file) => {
6349
+ const key = pathKey(file);
6350
+ if (loaded.has(key)) return;
6351
+ loaded.add(key);
6352
+ files.push(file);
6353
+ };
6354
+ const addPackage = (directory, entryFile, visiting) => {
6355
+ const key = pathKey(directory);
6356
+ if (visiting.has(key)) return;
6357
+ visiting.add(key);
6358
+ for (const dependency of dependencyNames(directory, host)) {
6359
+ const found = findPackage(dependency, directory, host);
6360
+ if (found) addPackage(found.directory, found.file, visiting);
6361
+ }
6362
+ addFile(entryFile);
6363
+ };
6364
+ for (const entry of config.types) {
6365
+ const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
6366
+ if (relative) {
6367
+ const target = resolve2(config.directory, entry);
6368
+ if (entry.endsWith(".luaut")) {
6369
+ if (host.readFile(target) !== void 0) addFile(target);
6370
+ else problems.push({ file: config.path, message: `Cannot find type library file '${entry}'`, ...entryPosition(config, entry) });
6371
+ continue;
6372
+ }
6373
+ const file = packageEntry(target, host);
6374
+ if (file) addPackage(target, file, /* @__PURE__ */ new Set());
6375
+ else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
6376
+ continue;
6377
+ }
6378
+ const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
6379
+ const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
6380
+ if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
6381
+ else {
6382
+ problems.push({
6383
+ file: config.path,
6384
+ message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
6385
+ ...entryPosition(config, entry)
6386
+ });
6387
+ }
6388
+ }
6389
+ return { files, problems };
6390
+ }
6391
+ var ENTRY_FILE = "index.d.luaut";
6392
+ function packageEntry(directory, host) {
6393
+ const manifest = readJson(join2(directory, "package.json"), host);
6394
+ const declared = manifest?.luaut?.types;
6395
+ const file = resolve2(directory, typeof declared === "string" ? declared : ENTRY_FILE);
6396
+ return host.readFile(file) !== void 0 ? file : void 0;
6397
+ }
6398
+ function findPackage(name, from, host) {
6399
+ let directory = resolve2(from);
6400
+ for (; ; ) {
6401
+ const candidate = join2(directory, "node_modules", ...name.split("/"));
6402
+ const file = packageEntry(candidate, host);
6403
+ if (file) return { directory: candidate, file };
6404
+ const parent = dirname2(directory);
6405
+ if (parent === directory) return void 0;
6406
+ directory = parent;
6407
+ }
6408
+ }
6409
+ function dependencyNames(directory, host) {
6410
+ const manifest = readJson(join2(directory, "package.json"), host);
6411
+ const names = /* @__PURE__ */ new Set();
6412
+ for (const field of ["dependencies", "peerDependencies"]) {
6413
+ const deps = manifest?.[field];
6414
+ if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
6415
+ }
6416
+ return [...names];
6417
+ }
6418
+ function readJson(path, host) {
6419
+ const text = host.readFile(path);
6420
+ if (text === void 0) return void 0;
6421
+ try {
6422
+ const value = JSON.parse(text);
6423
+ return value && typeof value === "object" ? value : void 0;
6424
+ } catch {
6425
+ return void 0;
6426
+ }
6427
+ }
6428
+ function entryPosition(config, entry) {
6429
+ return keyPosition(config.source, entry);
6430
+ }
6431
+ function pathKey(path) {
6432
+ const normalized = resolve2(path);
6433
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
6434
+ }
6435
+
6436
+ // src/project/modules.ts
6437
+ import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
6438
+ function moduleCandidates(fromFile, specifier, config) {
6439
+ const bases = specifier.startsWith("./") || specifier.startsWith("../") ? [resolve3(dirname3(fromFile), specifier)] : config ? aliasTargets(config, specifier) : [];
6440
+ return bases.flatMap((base) => base.endsWith(".luaut") ? [base] : [`${base}.luaut`, `${base}.d.luaut`, join3(base, "index.luaut")]);
6441
+ }
6442
+ function resolveModulePath(fromFile, specifier, config, host = nodeHost) {
6443
+ return moduleCandidates(fromFile, specifier, config).find((path) => host.readFile(path) !== void 0);
6444
+ }
6445
+ function aliasTargets(config, specifier) {
6446
+ let match;
6447
+ let prefixLength = -1;
6448
+ for (const pattern2 of Object.keys(config.paths)) {
6449
+ const star = pattern2.indexOf("*");
6450
+ if (star < 0) {
6451
+ if (pattern2 === specifier) {
6452
+ match = { pattern: pattern2, wildcard: "" };
6453
+ break;
6454
+ }
6455
+ continue;
6456
+ }
6457
+ const prefix = pattern2.slice(0, star);
6458
+ const suffix = pattern2.slice(star + 1);
6459
+ const fits = specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix);
6460
+ if (fits && prefix.length > prefixLength) {
6461
+ prefixLength = prefix.length;
6462
+ match = { pattern: pattern2, wildcard: specifier.slice(prefix.length, specifier.length - suffix.length) };
6463
+ }
6464
+ }
6465
+ if (!match) return [];
6466
+ const { pattern, wildcard } = match;
6467
+ return config.paths[pattern].map((target) => resolve3(config.baseUrl, target.replace("*", wildcard)));
6468
+ }
5832
6469
 
5833
- // src/lib/roblox.ts
5834
- import { readFileSync as readFileSync2 } from "fs";
5835
- import { fileURLToPath as fileURLToPath2 } from "url";
5836
- var robloxDefsPath = fileURLToPath2(new URL("./roblox.d.luaut", import.meta.url));
5837
- var robloxDefs = readFileSync2(robloxDefsPath, "utf8");
5838
- var robloxLib = parse(robloxDefs);
6470
+ // src/project/sourcemap.ts
6471
+ import { dirname as dirname4, extname, resolve as resolve4 } from "path";
6472
+ var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
6473
+ var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
6474
+ var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".luaut", ".luau", ".lua"]);
6475
+ function sourceMapTypes(text, path, options) {
6476
+ let root;
6477
+ try {
6478
+ root = JSON.parse(text);
6479
+ } catch (error) {
6480
+ return { problem: `Invalid sourcemap: ${error.message}` };
6481
+ }
6482
+ if (!isNode(root)) return { problem: "Invalid sourcemap: the root must be an object with 'name' and 'className'" };
6483
+ const directory = dirname4(resolve4(path));
6484
+ const lines = [];
6485
+ const aliasOfFile = /* @__PURE__ */ new Map();
6486
+ const used = /* @__PURE__ */ new Set();
6487
+ const aliasOfNode = /* @__PURE__ */ new Map();
6488
+ const aliasFor = (segments) => {
6489
+ const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
6490
+ let alias = base;
6491
+ for (let n = 2; used.has(alias); n++) alias = `${base}_${n}`;
6492
+ used.add(alias);
6493
+ return alias;
6494
+ };
6495
+ const visit = (node, segments, parent) => {
6496
+ const alias = aliasFor(segments);
6497
+ aliasOfNode.set(node, alias);
6498
+ for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey(resolve4(directory, filePath)), alias);
6499
+ const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
6500
+ const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
6501
+ const members = [];
6502
+ if (parent) members.push(`Parent: ${parent}`);
6503
+ const named = /* @__PURE__ */ new Set();
6504
+ for (const child of node.children ?? []) {
6505
+ if (!isNode(child)) continue;
6506
+ const childAlias = visit(child, [...segments, child.name], alias);
6507
+ if (!IDENTIFIER.test(child.name) || taken.has(child.name) || named.has(child.name)) continue;
6508
+ named.add(child.name);
6509
+ members.push(`${child.name}: ${childAlias}`);
6510
+ }
6511
+ lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
6512
+ return alias;
6513
+ };
6514
+ const rootAlias = visit(root, [root.name], void 0);
6515
+ if (root.className === "DataModel") {
6516
+ lines.push(`declare game: ${rootAlias}`);
6517
+ const workspace = (root.children ?? []).find((child) => isNode(child) && child.className === "Workspace");
6518
+ const workspaceAlias = workspace && aliasOfNode.get(workspace);
6519
+ if (workspaceAlias) lines.push(`declare workspace: ${workspaceAlias}`);
6520
+ }
6521
+ let program;
6522
+ try {
6523
+ program = parse(lines.join("\n"));
6524
+ } catch (error) {
6525
+ return { problem: `Could not turn the sourcemap into types: ${error.message}` };
6526
+ }
6527
+ return {
6528
+ types: {
6529
+ program,
6530
+ scriptFor(file) {
6531
+ const alias = aliasOfFile.get(fileKey(file));
6532
+ return alias ? parse(`declare script: ${alias}`) : void 0;
6533
+ }
6534
+ }
6535
+ };
6536
+ }
6537
+ function isNode(value) {
6538
+ if (!value || typeof value !== "object") return false;
6539
+ const node = value;
6540
+ return typeof node.name === "string" && typeof node.className === "string";
6541
+ }
6542
+ function fileKey(path) {
6543
+ const extension = extname(path);
6544
+ const bare = SCRIPT_EXTENSIONS.has(extension) ? path.slice(0, -extension.length) : path;
6545
+ const normalized = resolve4(bare);
6546
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
6547
+ }
5839
6548
 
5840
6549
  // src/index.ts
5841
- var defaultLibs = [luauLib, robloxLib];
5842
6550
  var luautparser = {
5843
6551
  tokenize,
5844
6552
  parseTokens,
@@ -5854,6 +6562,7 @@ var luautparser = {
5854
6562
  var index_default = luautparser;
5855
6563
  export {
5856
6564
  BinaryOperators,
6565
+ CONFIG_FILE_NAMES,
5857
6566
  Keywords,
5858
6567
  LexError,
5859
6568
  Operators,
@@ -5868,25 +6577,25 @@ export {
5868
6577
  bufferType,
5869
6578
  containsTypeParam,
5870
6579
  index_default as default,
5871
- defaultLibs,
5872
6580
  difference,
5873
6581
  equalTypes,
5874
6582
  falsyType,
6583
+ findConfig,
5875
6584
  fn,
5876
6585
  formatType,
5877
6586
  getBinding,
5878
6587
  intersection,
5879
6588
  isAssignable,
6589
+ isClassType,
5880
6590
  isGlobal,
5881
6591
  isPossiblyFalsy,
5882
6592
  isPossiblyTruthy,
5883
6593
  isUnassignedGlobal,
5884
6594
  literal,
5885
- luauDefs,
5886
- luauDefsPath,
5887
- luauLib,
6595
+ loadConfig,
5888
6596
  luautparser,
5889
6597
  matchInfer,
6598
+ moduleCandidates,
5890
6599
  moduleExports,
5891
6600
  narrowExclude,
5892
6601
  narrowFalsy,
@@ -5894,6 +6603,7 @@ export {
5894
6603
  narrowTruthy,
5895
6604
  neverType,
5896
6605
  nilType,
6606
+ nodeHost,
5897
6607
  numberType,
5898
6608
  objectType,
5899
6609
  optional,
@@ -5903,11 +6613,12 @@ export {
5903
6613
  parseTokens,
5904
6614
  parseWithRecovery,
5905
6615
  primitive,
5906
- robloxDefs,
5907
- robloxDefsPath,
5908
- robloxLib,
6616
+ resolveModulePath,
6617
+ resolveTypeLibraries,
5909
6618
  setAliasExpander,
6619
+ sourceMapTypes,
5910
6620
  stringType,
6621
+ stripJsonComments,
5911
6622
  substitute,
5912
6623
  templateMatches,
5913
6624
  threadType,