@thigasdevelopment/luam 0.2.0 → 0.4.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/luam.mjs CHANGED
@@ -212,7 +212,7 @@ var EXIT_DIAGNOSTICS = 1;
212
212
  var EXIT_USAGE = 2;
213
213
 
214
214
  // src/cli/usage.ts
215
- var VERSION = true ? "0.2.0" : "0.0.0-dev";
215
+ var VERSION = true ? "0.4.0" : "0.0.0-dev";
216
216
  var USAGE = [
217
217
  "luam \u2014 the Luam compiler for Multi Theft Auto resources.",
218
218
  "",
@@ -279,7 +279,7 @@ function classify(raw) {
279
279
  return { value, kind: value.length > 0 && Number.isFinite(Number(value)) ? "number" : "string" };
280
280
  }
281
281
  function parseEnvFile(source) {
282
- const entries = [];
282
+ const entries2 = [];
283
283
  const errors = [];
284
284
  source.split(/\r?\n/).forEach((raw, index) => {
285
285
  const trimmed = raw.trim();
@@ -293,9 +293,9 @@ function parseEnvFile(source) {
293
293
  return;
294
294
  }
295
295
  const { value, kind } = classify(match[2]);
296
- entries.push({ key: match[1], value, kind, line: line2 });
296
+ entries2.push({ key: match[1], value, kind, line: line2 });
297
297
  });
298
- return { entries, errors };
298
+ return { entries: entries2, errors };
299
299
  }
300
300
  function isSensitiveKey(key) {
301
301
  return SENSITIVE.test(key);
@@ -759,27 +759,27 @@ var PROCESS_GLOBAL = "process";
759
759
  var PROCESS_ENV = "process.env";
760
760
  var ENV_GLOBAL = "env";
761
761
  var VALUE_TYPES = { boolean: BOOLEAN, number: NUMBER, string: STRING };
762
- function envMembers(entries) {
762
+ function envMembers(entries2) {
763
763
  const members = /* @__PURE__ */ new Map();
764
- for (const entry of entries) {
764
+ for (const entry of entries2) {
765
765
  members.set(entry.key, { name: entry.key, type: VALUE_TYPES[entry.kind] });
766
766
  }
767
767
  return [...members.values()].sort((left, right) => left.name.localeCompare(right.name));
768
768
  }
769
- function environmentDeclaration(entries, origin) {
770
- const env = record(PROCESS_ENV, envMembers(entries), origin);
769
+ function environmentDeclaration(entries2, origin) {
770
+ const env = record(PROCESS_ENV, envMembers(entries2), origin);
771
771
  const process2 = record(PROCESS_GLOBAL, [{ name: ENV_GLOBAL, type: env }], origin);
772
772
  return { name: PROCESS_GLOBAL, environment: "server", source: "project", type: process2 };
773
773
  }
774
- function envDeclaration(entries, origin) {
775
- const env = record(ENV_GLOBAL, envMembers(entries), origin);
774
+ function envDeclaration(entries2, origin) {
775
+ const env = record(ENV_GLOBAL, envMembers(entries2), origin);
776
776
  return { name: ENV_GLOBAL, environment: "server", source: "project", type: env };
777
777
  }
778
- function projectDeclarations(entries, origin) {
779
- if (entries === null) {
778
+ function projectDeclarations(entries2, origin) {
779
+ if (entries2 === null) {
780
780
  return EMPTY_PROJECT_DECLARATIONS;
781
781
  }
782
- return { globals: [environmentDeclaration(entries, origin), envDeclaration(entries, origin)] };
782
+ return { globals: [environmentDeclaration(entries2, origin), envDeclaration(entries2, origin)] };
783
783
  }
784
784
 
785
785
  // ../compiler/src/checker/ambient.ts
@@ -792,9 +792,9 @@ function ambientFromRegistry(registry) {
792
792
  globals: registry.allGlobals()
793
793
  };
794
794
  }
795
- function withoutNames(entries, excluded) {
795
+ function withoutNames(entries2, excluded) {
796
796
  const names = new Set(excluded.map((entry) => entry.name));
797
- return entries.filter((entry) => !names.has(entry.name));
797
+ return entries2.filter((entry) => !names.has(entry.name));
798
798
  }
799
799
  function ownDeclarations(registry, ambient) {
800
800
  const all = ambientFromRegistry(registry);
@@ -916,6 +916,15 @@ function createNamed(name) {
916
916
  function createRecord(name, members, origin = null) {
917
917
  return { kind: "record", name, origin, members };
918
918
  }
919
+ function renameRecord(type, name) {
920
+ return type.kind === "record" ? createRecord(name, type.members, type.origin) : type;
921
+ }
922
+ function createObjectType(members) {
923
+ const keys = [...members].map(
924
+ ([name, member]) => member.kind === "optional" ? `${name}?: ${typeToString(member.element)}` : `${name}: ${typeToString(member)}`
925
+ );
926
+ return createRecord(keys.length === 0 ? "{}" : `{ ${keys.join(", ")} }`, members);
927
+ }
919
928
  function createFunction(parameters, returnType, minimumArguments2, isVariadic = false) {
920
929
  return { kind: "function", parameters, returnType, minimumArguments: minimumArguments2 ?? parameters.length, isVariadic };
921
930
  }
@@ -1022,6 +1031,21 @@ function isFunctionAssignable(source, target, options) {
1022
1031
  }
1023
1032
  return target.returnType.kind === "void" || isAssignable(source.returnType, target.returnType, options);
1024
1033
  }
1034
+ function isRecordAssignable(source, target, options) {
1035
+ for (const [name, member] of target.members) {
1036
+ const value = source.members.get(name);
1037
+ if (value === void 0) {
1038
+ if (member.kind !== "optional") {
1039
+ return false;
1040
+ }
1041
+ continue;
1042
+ }
1043
+ if (!isAssignable(value, member, options)) {
1044
+ return false;
1045
+ }
1046
+ }
1047
+ return true;
1048
+ }
1025
1049
  function isAssignable(source, target, options = { allowNil: false }) {
1026
1050
  if (source.kind === "any" || target.kind === "any" || target.kind === "unknown") {
1027
1051
  return true;
@@ -1053,6 +1077,9 @@ function isAssignable(source, target, options = { allowNil: false }) {
1053
1077
  if (target.kind === "table") {
1054
1078
  return isTableLike(source) || source.kind === "record";
1055
1079
  }
1080
+ if (target.kind === "record") {
1081
+ return source.kind === "record" ? isRecordAssignable(source, target, options) : source.kind === "table";
1082
+ }
1056
1083
  if (target.kind === "array") {
1057
1084
  return source.kind === "table" || source.kind === "array" && isAssignable(source.element, target.element, options);
1058
1085
  }
@@ -4831,6 +4858,14 @@ var DeclarationRegistry = class {
4831
4858
  lookupInterface(name) {
4832
4859
  return this.interfaces.get(name) ?? null;
4833
4860
  }
4861
+ interfaceExtends(name, target, visited = /* @__PURE__ */ new Set()) {
4862
+ const current = this.lookupInterface(name);
4863
+ if (current === null || visited.has(name)) {
4864
+ return false;
4865
+ }
4866
+ visited.add(name);
4867
+ return current.superInterfaces.some((parent) => parent === target || this.interfaceExtends(parent, target, visited));
4868
+ }
4834
4869
  declareEnum(info) {
4835
4870
  this.enums.set(info.name, info);
4836
4871
  }
@@ -4846,24 +4881,42 @@ var DeclarationRegistry = class {
4846
4881
  allEnums() {
4847
4882
  return [...this.enums.values()];
4848
4883
  }
4849
- collectMembers(className) {
4884
+ collectInterfaceMembers(name, collected, visited) {
4885
+ const current = this.lookupInterface(name);
4886
+ if (current === null || visited.has(current.name)) {
4887
+ return;
4888
+ }
4889
+ visited.add(current.name);
4890
+ for (const [memberName, member] of current.members) {
4891
+ if (!collected.has(memberName)) {
4892
+ collected.set(memberName, member);
4893
+ }
4894
+ }
4895
+ for (const parent of current.superInterfaces) {
4896
+ this.collectInterfaceMembers(parent, collected, visited);
4897
+ }
4898
+ }
4899
+ collectMembers(name) {
4850
4900
  const collected = /* @__PURE__ */ new Map();
4851
4901
  const visited = /* @__PURE__ */ new Set();
4852
- let current = this.lookupClass(className);
4902
+ let current = this.lookupClass(name);
4853
4903
  while (current !== null && !visited.has(current.name)) {
4854
- for (const [name, member] of current.members) {
4855
- if (!collected.has(name)) {
4856
- collected.set(name, member);
4904
+ for (const [name2, member] of current.members) {
4905
+ if (!collected.has(name2)) {
4906
+ collected.set(name2, member);
4857
4907
  }
4858
4908
  }
4859
4909
  visited.add(current.name);
4860
4910
  current = current.superClass === null ? null : this.lookupClass(current.superClass);
4861
4911
  }
4912
+ if (current === null && this.lookupClass(name) === null) {
4913
+ this.collectInterfaceMembers(name, collected, visited);
4914
+ }
4862
4915
  return [...collected.values()];
4863
4916
  }
4864
- lookupMember(className, member) {
4917
+ lookupMember(name, member) {
4865
4918
  const visited = /* @__PURE__ */ new Set();
4866
- let current = this.lookupClass(className);
4919
+ let current = this.lookupClass(name);
4867
4920
  while (current !== null && !visited.has(current.name)) {
4868
4921
  const found = current.members.get(member);
4869
4922
  if (found !== void 0) {
@@ -4872,7 +4925,9 @@ var DeclarationRegistry = class {
4872
4925
  visited.add(current.name);
4873
4926
  current = current.superClass === null ? null : this.lookupClass(current.superClass);
4874
4927
  }
4875
- return null;
4928
+ const collected = /* @__PURE__ */ new Map();
4929
+ this.collectInterfaceMembers(name, collected, visited);
4930
+ return collected.get(member) ?? null;
4876
4931
  }
4877
4932
  };
4878
4933
 
@@ -4987,6 +5042,7 @@ var CheckContext = class {
4987
5042
  methodStack = [];
4988
5043
  projectEnvironments = /* @__PURE__ */ new Map();
4989
5044
  ambientClasses = /* @__PURE__ */ new Set();
5045
+ ambientInterfaces = /* @__PURE__ */ new Set();
4990
5046
  constructor(mode, environment, ambient = EMPTY_AMBIENT, project = EMPTY_PROJECT_DECLARATIONS, oop = false) {
4991
5047
  this.mode = mode;
4992
5048
  this.environment = environment;
@@ -5008,6 +5064,9 @@ var CheckContext = class {
5008
5064
  isAmbientClass(name) {
5009
5065
  return this.ambientClasses.has(name);
5010
5066
  }
5067
+ isAmbientInterface(name) {
5068
+ return this.ambientInterfaces.has(name);
5069
+ }
5011
5070
  noteExternalReference(name, position2) {
5012
5071
  if (!this.externalReferences.has(name)) {
5013
5072
  this.externalReferences.set(name, position2);
@@ -5057,6 +5116,16 @@ var CheckContext = class {
5057
5116
  if (annotation.kind === "type-union") {
5058
5117
  return createUnion(annotation.options.map((option) => this.resolveAnnotation(option)));
5059
5118
  }
5119
+ if (annotation.kind === "type-object") {
5120
+ const members = /* @__PURE__ */ new Map();
5121
+ for (const member of annotation.members) {
5122
+ members.set(member.name, this.resolveAnnotation(member.annotation));
5123
+ }
5124
+ return createObjectType(members);
5125
+ }
5126
+ if (annotation.kind === "type-tuple") {
5127
+ return createTuple(annotation.elements.map((element) => this.resolveAnnotation(element)));
5128
+ }
5060
5129
  if (annotation.kind === "type-function") {
5061
5130
  const parameters = annotation.parameters.map((parameter) => this.resolveAnnotation(parameter));
5062
5131
  const optional = parameters.findIndex((parameter) => parameter.kind === "optional");
@@ -5088,6 +5157,7 @@ var CheckContext = class {
5088
5157
  this.binder.declareGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: info.position });
5089
5158
  }
5090
5159
  for (const info of ambient.interfaces) {
5160
+ this.ambientInterfaces.add(info.name);
5091
5161
  this.declarations.declareInterface(info);
5092
5162
  }
5093
5163
  for (const info of ambient.enums) {
@@ -5387,7 +5457,7 @@ function expandClassDecorators(context, statement, fieldTypes) {
5387
5457
  const occupied = new Map(statement.members.filter((member) => member.kind === "class-method").map((member) => [member.name, member.name]));
5388
5458
  const classDecorators = validateDecorators(context, statement.decorators, "class");
5389
5459
  for (const member of statement.members) {
5390
- if (member.kind === "class-method") {
5460
+ if (member.kind === "class-method" || member.name === "constructor") {
5391
5461
  validateDecorators(context, member.decorators, "method");
5392
5462
  continue;
5393
5463
  }
@@ -5969,7 +6039,11 @@ function resolveNamedMember(context, name, expression) {
5969
6039
  return resolveMtaMember(context, name, expression.property, expression.position)?.type ?? ANY_TYPE;
5970
6040
  }
5971
6041
  const contract = context.declarations.lookupClass(name) === null ? context.declarations.lookupInterface(name) : null;
5972
- return contract === null ? null : checkInterfaceMember(context, name, contract.members, expression);
6042
+ if (contract === null) {
6043
+ return null;
6044
+ }
6045
+ const members = new Map(context.declarations.collectMembers(name).map((member2) => [member2.name, member2]));
6046
+ return checkInterfaceMember(context, name, members, expression);
5973
6047
  }
5974
6048
  var NATIVE_CONSTRUCTOR = "new";
5975
6049
  function nativeConstructor(context, name) {
@@ -6334,6 +6408,10 @@ function registerMembers(context, info, statement) {
6334
6408
  return generated;
6335
6409
  }
6336
6410
  function checkMethodBody(context, info, member) {
6411
+ const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
6412
+ if (explicitSelf !== void 0) {
6413
+ context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
6414
+ }
6337
6415
  context.pushClassMethod({ className: info.name, methodName: member.name });
6338
6416
  checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, createNamed(info.name));
6339
6417
  context.popClassMethod();
@@ -6360,7 +6438,7 @@ function checkInterfaces(context, info) {
6360
6438
  context.report("check-unknown-interface", `Class "${info.name}" implements "${name}", which is not defined.`, info.position);
6361
6439
  continue;
6362
6440
  }
6363
- for (const member of contract.members.values()) {
6441
+ for (const member of context.declarations.collectMembers(contract.name)) {
6364
6442
  checkContract(context, info, name, member);
6365
6443
  }
6366
6444
  }
@@ -6418,11 +6496,46 @@ function checkInterfaceDeclaration(context, statement) {
6418
6496
  return;
6419
6497
  }
6420
6498
  const members = /* @__PURE__ */ new Map();
6499
+ for (const name of new Set(statement.superInterfaces)) {
6500
+ const parent = context.declarations.lookupInterface(name);
6501
+ if (name === statement.name || context.declarations.interfaceExtends(name, statement.name)) {
6502
+ context.report("check-interface-cycle", `Interface "${statement.name}" creates an inheritance cycle through "${name}".`, statement.position);
6503
+ } else if (parent === null) {
6504
+ context.noteExternalReference(name, statement.position);
6505
+ context.report("check-unknown-interface", `Interface "${statement.name}" extends "${name}", which is not defined.`, statement.position);
6506
+ } else if (context.isAmbientInterface(name)) {
6507
+ context.noteExternalReference(name, statement.position);
6508
+ }
6509
+ }
6510
+ if (new Set(statement.superInterfaces).size !== statement.superInterfaces.length) {
6511
+ context.report("check-duplicate-interface-parent", `Interface "${statement.name}" extends the same interface more than once.`, statement.position);
6512
+ }
6421
6513
  for (const member of statement.members) {
6422
6514
  const type = member.kind === "interface-field" ? context.resolveAnnotation(member.annotation) : buildFunctionType(context, member.parameters, member.returnAnnotation);
6423
- members.set(member.name, { name: member.name, type, isMethod: member.kind === "interface-method", position: member.position });
6515
+ const info = { name: member.name, type, isMethod: member.kind === "interface-method", position: member.position };
6516
+ if (members.has(member.name)) {
6517
+ context.report("check-duplicate-interface-member", `Interface "${statement.name}" declares member "${member.name}" more than once.`, member.position);
6518
+ } else {
6519
+ members.set(member.name, info);
6520
+ }
6424
6521
  }
6425
- context.declarations.declareInterface({ name: statement.name, members, position: statement.position });
6522
+ const inherited = /* @__PURE__ */ new Map();
6523
+ for (const parent of statement.superInterfaces) {
6524
+ for (const member of context.declarations.collectMembers(parent)) {
6525
+ const existing = members.get(member.name) ?? inherited.get(member.name);
6526
+ if (existing !== void 0 && (existing.isMethod !== member.isMethod || typeToString(existing.type) !== typeToString(member.type))) {
6527
+ context.report("check-conflicting-interface-member", `Interface "${statement.name}" inherits conflicting declarations of "${member.name}".`, statement.position);
6528
+ } else if (existing === void 0) {
6529
+ inherited.set(member.name, member);
6530
+ }
6531
+ }
6532
+ }
6533
+ context.declarations.declareInterface({
6534
+ name: statement.name,
6535
+ superInterfaces: statement.superInterfaces,
6536
+ members,
6537
+ position: statement.position
6538
+ });
6426
6539
  }
6427
6540
  function checkEnumDeclaration(context, statement) {
6428
6541
  if (context.declarations.lookupEnum(statement.name) !== null) {
@@ -6582,6 +6695,23 @@ function checkReturn(context, statement) {
6582
6695
  }
6583
6696
  return;
6584
6697
  }
6698
+ if (expected.kind === "tuple") {
6699
+ if (valueTypes.length !== expected.elements.length) {
6700
+ const message = `This function declares ${expected.elements.length} return values but returns ${valueTypes.length}.`;
6701
+ context.report("check-return-mismatch", message, statement.position);
6702
+ return;
6703
+ }
6704
+ valueTypes.forEach((type, index) => {
6705
+ const value2 = statement.values[Math.min(index, statement.values.length - 1)];
6706
+ context.expectAssignable(type, expected.elements[index] ?? ANY_TYPE, value2?.position ?? statement.position, `Return value ${index + 1}`);
6707
+ });
6708
+ return;
6709
+ }
6710
+ if (valueTypes.length > 1) {
6711
+ const message = `This function declares one return value of type "${typeToString(expected)}" but returns ${valueTypes.length} values.`;
6712
+ context.report("check-return-mismatch", message, statement.position);
6713
+ return;
6714
+ }
6585
6715
  const first = valueTypes[0];
6586
6716
  const value = statement.values[0];
6587
6717
  if (first === void 0 || value === void 0) {
@@ -6628,9 +6758,12 @@ function checkStatement(context, statement) {
6628
6758
  return checkNumericFor(context, statement);
6629
6759
  case "generic-for-statement":
6630
6760
  return checkGenericFor(context, statement);
6631
- case "type-alias-statement":
6632
- context.binder.declareAlias(statement.name, context.resolveAnnotation(statement.annotation));
6761
+ case "type-alias-statement": {
6762
+ const resolved = context.resolveAnnotation(statement.annotation);
6763
+ const named2 = statement.annotation.kind === "type-object" ? renameRecord(resolved, statement.name) : resolved;
6764
+ context.binder.declareAlias(statement.name, named2);
6633
6765
  return;
6766
+ }
6634
6767
  case "declare-statement":
6635
6768
  return checkDeclareStatement(context, statement);
6636
6769
  case "class-declaration":
@@ -6988,22 +7121,22 @@ function emitMethod(state, className, member) {
6988
7121
  return withSymbol(state, `${className}:${member.name}`, () => emitFunctionBody(state, [self, ...member.parameters], member.body, `${member.name} = function`));
6989
7122
  }
6990
7123
  function emitMembers(state, statement) {
6991
- const entries = [];
7124
+ const entries2 = [];
6992
7125
  state.indent += 1;
6993
7126
  for (const member of statement.members) {
6994
7127
  if (member.kind === "class-method") {
6995
- entries.push(indentLine(state, `${markSource(state, member.position.line, `${statement.name}:${member.name}`)}${emitMethod(state, statement.name, member)}`));
7128
+ entries2.push(indentLine(state, `${markSource(state, member.position.line, `${statement.name}:${member.name}`)}${emitMethod(state, statement.name, member)}`));
6996
7129
  continue;
6997
7130
  }
6998
7131
  if (member.value !== null) {
6999
- entries.push(indentLine(state, `${markSource(state, member.position.line, statement.name)}${member.name} = ${emitExpression(state, member.value)}`));
7132
+ entries2.push(indentLine(state, `${markSource(state, member.position.line, statement.name)}${member.name} = ${emitExpression(state, member.value)}`));
7000
7133
  }
7001
7134
  }
7002
7135
  for (const generated of state.generatedMembers.get(statement) ?? []) {
7003
- entries.push(indentLine(state, `${markSource(state, generated.position.line, `${statement.name}:${generated.name}`)}${emitMethod(state, statement.name, generated)}`));
7136
+ entries2.push(indentLine(state, `${markSource(state, generated.position.line, `${statement.name}:${generated.name}`)}${emitMethod(state, statement.name, generated)}`));
7004
7137
  }
7005
7138
  state.indent -= 1;
7006
- return entries;
7139
+ return entries2;
7007
7140
  }
7008
7141
  function emitClassHeader(statement) {
7009
7142
  const name = emitString(statement.name);
@@ -7012,11 +7145,11 @@ function emitClassHeader(statement) {
7012
7145
  function emitClassDeclaration(state, statement) {
7013
7146
  requireHelper(state, "class");
7014
7147
  const header = emitClassHeader(statement);
7015
- const entries = emitMembers(state, statement);
7016
- if (entries.length === 0) {
7148
+ const entries2 = emitMembers(state, statement);
7149
+ if (entries2.length === 0) {
7017
7150
  return `${header} {}`;
7018
7151
  }
7019
- return [`${header} {`, entries.join(",\n"), indentLine(state, "}")].join("\n");
7152
+ return [`${header} {`, entries2.join(",\n"), indentLine(state, "}")].join("\n");
7020
7153
  }
7021
7154
  function emitEnumDeclaration(state, statement) {
7022
7155
  if (!state.references.has(statement.name)) {
@@ -7781,13 +7914,45 @@ function parseFunctionType(stream) {
7781
7914
  return { kind: "type-function", parameters, isVariadic, returnType, position: position2 };
7782
7915
  }
7783
7916
  function parseGroupedType(stream) {
7784
- stream.expect("punctuation", "(");
7917
+ const position2 = stream.expect("punctuation", "(").position;
7785
7918
  const inner = parseTypeAnnotation(stream);
7786
- if (stream.check("punctuation", ",")) {
7787
- throw stream.error("A parenthesized type must contain a single type.", "parse-invalid-type");
7919
+ if (!stream.match("punctuation", ",")) {
7920
+ stream.expect("punctuation", ")");
7921
+ return inner;
7788
7922
  }
7923
+ const elements = [inner];
7924
+ do {
7925
+ elements.push(parseTypeAnnotation(stream));
7926
+ } while (stream.match("punctuation", ","));
7789
7927
  stream.expect("punctuation", ")");
7790
- return inner;
7928
+ return { kind: "type-tuple", elements, position: position2 };
7929
+ }
7930
+ function parseObjectMember(stream) {
7931
+ const token = stream.expectName();
7932
+ const annotation = parseNamedAnnotation(stream, "field");
7933
+ if (annotation === null) {
7934
+ throw stream.error(`Key "${token.value}" of an object type requires a type annotation.`, "parse-invalid-type");
7935
+ }
7936
+ return { name: token.value, annotation, position: token.position };
7937
+ }
7938
+ function parseObjectType(stream) {
7939
+ const position2 = stream.expect("punctuation", "{").position;
7940
+ const members = [];
7941
+ const declared = /* @__PURE__ */ new Set();
7942
+ while (!stream.check("punctuation", "}") && !stream.isEof()) {
7943
+ if (stream.match("punctuation", ";") || stream.match("punctuation", ",")) {
7944
+ continue;
7945
+ }
7946
+ const member = parseObjectMember(stream);
7947
+ if (declared.has(member.name)) {
7948
+ stream.report("parse-duplicate-key", `Object type key "${member.name}" is declared more than once.`, member.position);
7949
+ continue;
7950
+ }
7951
+ declared.add(member.name);
7952
+ members.push(member);
7953
+ }
7954
+ stream.expect("punctuation", "}");
7955
+ return { kind: "type-object", members, position: position2 };
7791
7956
  }
7792
7957
  function parsePrimaryType(stream) {
7793
7958
  if (stream.check("keyword", "function")) {
@@ -7796,6 +7961,9 @@ function parsePrimaryType(stream) {
7796
7961
  if (stream.check("identifier", FUNCTION_TYPE)) {
7797
7962
  return parseFunctionType(stream);
7798
7963
  }
7964
+ if (stream.check("punctuation", "{")) {
7965
+ return parseObjectType(stream);
7966
+ }
7799
7967
  return stream.check("punctuation", "(") ? parseGroupedType(stream) : parseTypeName(stream);
7800
7968
  }
7801
7969
  function parsePostfixType(stream) {
@@ -8125,16 +8293,24 @@ function parseForStatement(stream) {
8125
8293
  }
8126
8294
 
8127
8295
  // ../compiler/src/parser/recovery.ts
8128
- function recoverInBlock(stream, error) {
8296
+ var BRACE_TERMINATORS = /* @__PURE__ */ new Set(["}"]);
8297
+ function isTerminator(stream, terminators) {
8298
+ const token = stream.current();
8299
+ if (token.kind !== "punctuation" && token.kind !== "keyword") {
8300
+ return false;
8301
+ }
8302
+ return terminators.has(token.value);
8303
+ }
8304
+ function recoverInBlock(stream, error, terminators = BRACE_TERMINATORS) {
8129
8305
  stream.diagnostics.push(error.diagnostic);
8130
8306
  if (stream.isEof()) {
8131
8307
  return;
8132
8308
  }
8133
8309
  const start = stream.current();
8134
- while (!stream.isEof() && !stream.check("punctuation", "}") && stream.current().position.line === start.position.line) {
8310
+ while (!stream.isEof() && !isTerminator(stream, terminators) && stream.current().position.line === start.position.line) {
8135
8311
  stream.next();
8136
8312
  }
8137
- if (!stream.isEof() && !stream.check("punctuation", "}") && stream.current().position.offset === start.position.offset) {
8313
+ if (!stream.isEof() && !isTerminator(stream, terminators) && stream.current().position.offset === start.position.offset) {
8138
8314
  stream.next();
8139
8315
  }
8140
8316
  }
@@ -8244,7 +8420,10 @@ function isDeclarationStart(stream) {
8244
8420
  if (offset > 0 && token.value !== "class") {
8245
8421
  return false;
8246
8422
  }
8247
- return token.value === "class" ? isClassHeader(stream, offset) : stream.checkAhead(offset + 2, "punctuation", "{");
8423
+ if (token.value === "class") {
8424
+ return isClassHeader(stream, offset);
8425
+ }
8426
+ return token.value === "interface" ? stream.checkAhead(offset + 2, "punctuation", "{") || stream.checkAhead(offset + 2, "keyword", "extends") : stream.checkAhead(offset + 2, "punctuation", "{");
8248
8427
  }
8249
8428
  function skipDecoratorArguments(stream) {
8250
8429
  let depth = 0;
@@ -8271,6 +8450,21 @@ function parseDecorators(stream) {
8271
8450
  return decorators;
8272
8451
  }
8273
8452
  function parseClassMethod(stream, token, decorators) {
8453
+ stream.expect("operator", "=");
8454
+ const expression = parseFunctionExpression(stream);
8455
+ return {
8456
+ kind: "class-method",
8457
+ name: token.value,
8458
+ isConstructor: token.value === "constructor",
8459
+ isSynthetic: false,
8460
+ parameters: expression.parameters,
8461
+ returnAnnotation: expression.returnAnnotation,
8462
+ body: expression.body,
8463
+ decorators,
8464
+ position: token.position
8465
+ };
8466
+ }
8467
+ function parseLegacyClassMethod(stream, token, decorators) {
8274
8468
  const parameters = parseParameters(stream);
8275
8469
  const returnAnnotation = parseOptionalAnnotation(stream);
8276
8470
  const body = parseBraceBlock(stream);
@@ -8293,6 +8487,9 @@ function parseClassMember(stream) {
8293
8487
  const decorators = parseDecorators(stream);
8294
8488
  const token = stream.expectName();
8295
8489
  if (stream.check("punctuation", "(")) {
8490
+ return parseLegacyClassMethod(stream, token, decorators);
8491
+ }
8492
+ if (stream.check("operator", "=") && stream.checkAhead(1, "keyword", "function")) {
8296
8493
  return parseClassMethod(stream, token, decorators);
8297
8494
  }
8298
8495
  const annotation = parseFieldAnnotation(stream);
@@ -8348,7 +8545,13 @@ function parseInterfaceMember(stream) {
8348
8545
  function parseInterfaceDeclaration(stream) {
8349
8546
  const position2 = stream.next().position;
8350
8547
  const name = stream.expect("identifier").value;
8548
+ const superInterfaces = [];
8351
8549
  const members = [];
8550
+ if (stream.match("keyword", "extends")) {
8551
+ do {
8552
+ superInterfaces.push(stream.expect("identifier").value);
8553
+ } while (stream.match("punctuation", ","));
8554
+ }
8352
8555
  stream.expect("punctuation", "{");
8353
8556
  while (!stream.check("punctuation", "}") && !stream.isEof()) {
8354
8557
  if (stream.match("punctuation", ";") || stream.match("punctuation", ",")) {
@@ -8364,7 +8567,7 @@ function parseInterfaceDeclaration(stream) {
8364
8567
  }
8365
8568
  }
8366
8569
  stream.expect("punctuation", "}");
8367
- return { kind: "interface-declaration", name, members, position: position2 };
8570
+ return { kind: "interface-declaration", name, superInterfaces, members, position: position2 };
8368
8571
  }
8369
8572
  function parseEnumDeclaration(stream) {
8370
8573
  const position2 = stream.next().position;
@@ -8564,14 +8767,14 @@ function parseStatement(stream) {
8564
8767
  }
8565
8768
  return parseExpressionStatement(stream);
8566
8769
  }
8567
- function parseBlockStatement(stream) {
8770
+ function parseBlockStatement(stream, terminators = BRACE_TERMINATORS) {
8568
8771
  try {
8569
8772
  return parseStatement(stream);
8570
8773
  } catch (error) {
8571
8774
  if (!(error instanceof ParserError)) {
8572
8775
  throw error;
8573
8776
  }
8574
- recoverInBlock(stream, error);
8777
+ recoverInBlock(stream, error, terminators);
8575
8778
  return null;
8576
8779
  }
8577
8780
  }
@@ -8604,7 +8807,14 @@ function parseBlock(stream, terminators) {
8604
8807
  if (stream.current().kind === "keyword" && stop.has(stream.current().value)) {
8605
8808
  return body;
8606
8809
  }
8607
- const statement = parseStatement(stream);
8810
+ const offset = stream.current().position.offset;
8811
+ const statement = parseBlockStatement(stream, stop);
8812
+ if (statement === null) {
8813
+ if (stream.current().position.offset === offset) {
8814
+ return body;
8815
+ }
8816
+ continue;
8817
+ }
8608
8818
  body.push(statement);
8609
8819
  if (statement.kind === "return-statement") {
8610
8820
  return body;
@@ -8751,7 +8961,7 @@ function classText(info) {
8751
8961
  return `class ${info.name}:${info.superClass ?? ""}:${[...info.interfaces].sort(compareText).join(",")}{${membersText(info.members)}}`;
8752
8962
  }
8753
8963
  function interfaceText(info) {
8754
- return `interface ${info.name}{${membersText(info.members)}}`;
8964
+ return `interface ${info.name}:${[...info.superInterfaces].sort(compareText).join(",")}{${membersText(info.members)}}`;
8755
8965
  }
8756
8966
  function enumText(info) {
8757
8967
  return `enum ${info.name}{${info.members.join(",")}}`;
@@ -8809,8 +9019,8 @@ function attribute(name, value) {
8809
9019
  function textElement(name, value) {
8810
9020
  return `${INDENT2}<${name}>${escapeXml(value)}</${name}>`;
8811
9021
  }
8812
- function section(text, entries) {
8813
- return entries.length === 0 ? [] : [`${INDENT2}<!-- ${text} -->`, ...entries];
9022
+ function section(text, entries2) {
9023
+ return entries2.length === 0 ? [] : [`${INDENT2}<!-- ${text} -->`, ...entries2];
8814
9024
  }
8815
9025
  function infoElement(info) {
8816
9026
  const attributes = info.author === void 0 ? [] : [attribute("author", info.author)];
@@ -8949,16 +9159,16 @@ function createAmbientResolver(collected) {
8949
9159
  const visible = /* @__PURE__ */ new Map();
8950
9160
  const merged = /* @__PURE__ */ new Map();
8951
9161
  for (const environment of ALL_ENVIRONMENTS) {
8952
- const entries = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
8953
- visible.set(environment, entries);
8954
- merged.set(environment, mergeAmbient(entries.map((entry) => entry.declarations)));
9162
+ const entries2 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
9163
+ visible.set(environment, entries2);
9164
+ merged.set(environment, mergeAmbient(entries2.map((entry) => entry.declarations)));
8955
9165
  }
8956
9166
  return (entry) => {
8957
9167
  if (declaresNothing(entry.declarations)) {
8958
9168
  return merged.get(entry.environment) ?? EMPTY_AMBIENT;
8959
9169
  }
8960
- const entries = visible.get(entry.environment) ?? [];
8961
- return mergeAmbient(entries.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
9170
+ const entries2 = visible.get(entry.environment) ?? [];
9171
+ return mergeAmbient(entries2.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
8962
9172
  };
8963
9173
  }
8964
9174
  function compileModule(file, ambient, context) {
@@ -9245,12 +9455,12 @@ var MISSING_LOAD_ORDER = 'is listed in "loadOrder" but no source file or asset m
9245
9455
  function missingEntry(entry) {
9246
9456
  return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-missing", `"${entry}" ${MISSING_LOAD_ORDER}`, FILE_START) };
9247
9457
  }
9248
- function resolveLoadOrder(entries, scripts, assets) {
9458
+ function resolveLoadOrder(entries2, scripts, assets) {
9249
9459
  const byScript = new Map(scripts.map((script) => [normalizePath(script.source), script]));
9250
9460
  const byAsset = new Map(assets.map((asset) => [normalizePath(asset.source), asset]));
9251
9461
  const resolved = { scripts: [], assets: [], diagnostics: [] };
9252
9462
  const seen = /* @__PURE__ */ new Set();
9253
- for (const entry of entries) {
9463
+ for (const entry of entries2) {
9254
9464
  const path = normalizePath(entry).replace(/^\.\//, "");
9255
9465
  const script = byScript.get(path);
9256
9466
  const asset = byAsset.get(path);
@@ -9482,8 +9692,8 @@ function materialize(build, resource, includeMap) {
9482
9692
  const bundled = materializeBundles(resource, build.bundles, helperContent);
9483
9693
  return { ...build, scripts: bundled.scripts, map: includeMap ? bundled.map : null };
9484
9694
  }
9485
- function diagnosticSources(files, entries) {
9486
- const paths = new Set(entries.map((entry) => entry.path));
9695
+ function diagnosticSources(files, entries2) {
9696
+ const paths = new Set(entries2.map((entry) => entry.path));
9487
9697
  return new Map(files.filter((file) => paths.has(file.path)).map((file) => [file.path, file.source]));
9488
9698
  }
9489
9699
  function runCompile(root, config, options = {}) {
@@ -9644,13 +9854,13 @@ function isSearchable(name) {
9644
9854
  return !name.startsWith(".") && !SKIPPED_DIRECTORIES.has(name);
9645
9855
  }
9646
9856
  function collectResourceMaps(directory, found) {
9647
- let entries;
9857
+ let entries2;
9648
9858
  try {
9649
- entries = readdirSync3(directory, { withFileTypes: true });
9859
+ entries2 = readdirSync3(directory, { withFileTypes: true });
9650
9860
  } catch {
9651
9861
  return;
9652
9862
  }
9653
- for (const entry of entries) {
9863
+ for (const entry of entries2) {
9654
9864
  const absolute = join3(directory, entry.name);
9655
9865
  if (entry.isDirectory()) {
9656
9866
  if (isSearchable(entry.name)) {
@@ -9995,9 +10205,9 @@ function pluralize(count, singular, plural = `${singular}s`) {
9995
10205
  var MAX_ENTRIES_PER_FILE = 10;
9996
10206
  var MAX_FILES = 10;
9997
10207
  var GUTTER_WIDTH = 6;
9998
- function groupByFile(entries) {
10208
+ function groupByFile(entries2) {
9999
10209
  const groups = /* @__PURE__ */ new Map();
10000
- for (const entry of entries) {
10210
+ for (const entry of entries2) {
10001
10211
  const group = groups.get(entry.path);
10002
10212
  if (group === void 0) {
10003
10213
  groups.set(entry.path, [entry]);
@@ -10044,19 +10254,19 @@ function emit2(reporter, entry, block) {
10044
10254
  }
10045
10255
  reporter.rawWarn(block);
10046
10256
  }
10047
- function reportGroup(reporter, path, entries, options) {
10257
+ function reportGroup(reporter, path, entries2, options) {
10048
10258
  const limit = options.maxPerFile ?? MAX_ENTRIES_PER_FILE;
10049
10259
  const source = options.sources.get(path);
10050
10260
  reporter.raw(reporter.style.paint("strong", path));
10051
- for (const entry of entries.slice(0, limit)) {
10261
+ for (const entry of entries2.slice(0, limit)) {
10052
10262
  emit2(reporter, entry, entryBlock(reporter, entry, source));
10053
10263
  }
10054
- if (entries.length > limit) {
10055
- reporter.detail(` ... and ${pluralize(entries.length - limit, "more diagnostic")} in this file.`);
10264
+ if (entries2.length > limit) {
10265
+ reporter.detail(` ... and ${pluralize(entries2.length - limit, "more diagnostic")} in this file.`);
10056
10266
  }
10057
10267
  }
10058
- function reportGroupedDiagnostics(reporter, entries, options) {
10059
- const groups = [...groupByFile(entries)];
10268
+ function reportGroupedDiagnostics(reporter, entries2, options) {
10269
+ const groups = [...groupByFile(entries2)];
10060
10270
  const limit = options.maxFiles ?? MAX_FILES;
10061
10271
  for (const [path, group] of groups.slice(0, limit)) {
10062
10272
  reportGroup(reporter, path, group, options);
@@ -10072,9 +10282,9 @@ function formatFileDiagnostic(entry) {
10072
10282
  const location = `${path}:${diagnostic.position.line}:${diagnostic.position.column}`;
10073
10283
  return `${location} ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`;
10074
10284
  }
10075
- function countFileDiagnostics(entries) {
10076
- const errors = entries.filter((entry) => entry.diagnostic.severity === "error").length;
10077
- return { errors, warnings: entries.length - errors };
10285
+ function countFileDiagnostics(entries2) {
10286
+ const errors = entries2.filter((entry) => entry.diagnostic.severity === "error").length;
10287
+ return { errors, warnings: entries2.length - errors };
10078
10288
  }
10079
10289
  function countCliDiagnostics(diagnostics) {
10080
10290
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
@@ -10090,8 +10300,8 @@ function reportCliDiagnostics(reporter, diagnostics) {
10090
10300
  reporter.warn(line2);
10091
10301
  }
10092
10302
  }
10093
- function reportPlainFileDiagnostics(reporter, entries) {
10094
- for (const entry of entries) {
10303
+ function reportPlainFileDiagnostics(reporter, entries2) {
10304
+ for (const entry of entries2) {
10095
10305
  const line2 = formatFileDiagnostic(entry);
10096
10306
  if (entry.diagnostic.severity === "error") {
10097
10307
  reporter.rawError(line2);
@@ -10100,12 +10310,12 @@ function reportPlainFileDiagnostics(reporter, entries) {
10100
10310
  reporter.rawWarn(line2);
10101
10311
  }
10102
10312
  }
10103
- function reportFileDiagnostics(reporter, entries, sources) {
10313
+ function reportFileDiagnostics(reporter, entries2, sources) {
10104
10314
  if (!reporter.capability.interactive) {
10105
- reportPlainFileDiagnostics(reporter, entries);
10315
+ reportPlainFileDiagnostics(reporter, entries2);
10106
10316
  return;
10107
10317
  }
10108
- reportGroupedDiagnostics(reporter, entries, { sources });
10318
+ reportGroupedDiagnostics(reporter, entries2, { sources });
10109
10319
  }
10110
10320
  function formatCounts(counts) {
10111
10321
  return `${pluralize(counts.errors, "error")}, ${pluralize(counts.warnings, "warning")}`;
@@ -11002,11 +11212,11 @@ function readStringArray(source, field, diagnostics, scope = "") {
11002
11212
  if (!Array.isArray(value) || value.length === 0) {
11003
11213
  return report2(diagnostics, `${scope}${field}`, "a non-empty array of strings", value);
11004
11214
  }
11005
- const entries = value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
11006
- if (entries.length !== value.length) {
11215
+ const entries2 = value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
11216
+ if (entries2.length !== value.length) {
11007
11217
  return report2(diagnostics, `${scope}${field}`, "a non-empty array of strings", value);
11008
11218
  }
11009
- return entries.map((entry) => entry.trim());
11219
+ return entries2.map((entry) => entry.trim());
11010
11220
  }
11011
11221
  function readObject(source, field, diagnostics, scope = "") {
11012
11222
  const value = source[field];
@@ -11376,10 +11586,70 @@ function runTraceCommand(root, reporter, options) {
11376
11586
  }
11377
11587
 
11378
11588
  // src/editor/editor-service.ts
11379
- import { spawnSync } from "node:child_process";
11380
11589
  import { mkdtempSync, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
11381
11590
  import { tmpdir } from "node:os";
11382
- import { join as join5 } from "node:path";
11591
+ import { join as join6 } from "node:path";
11592
+
11593
+ // src/editor/editor-command.ts
11594
+ import { spawnSync } from "node:child_process";
11595
+ import { statSync as statSync4 } from "node:fs";
11596
+ import { extname, isAbsolute as isAbsolute7, join as join5 } from "node:path";
11597
+ var WINDOWS_SEPARATOR = ";";
11598
+ var DEFAULT_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
11599
+ var SHELL_EXTENSIONS = /* @__PURE__ */ new Set([".bat", ".cmd"]);
11600
+ var SPAWN_OPTIONS = { encoding: "utf8", shell: false, windowsHide: true, maxBuffer: 1024 * 1024 };
11601
+ function entries(value) {
11602
+ return value.split(WINDOWS_SEPARATOR).map((entry) => entry.trim().replace(/^"|"$/gu, "")).filter((entry) => entry.length > 0);
11603
+ }
11604
+ function present(path) {
11605
+ try {
11606
+ return statSync4(path).isFile();
11607
+ } catch {
11608
+ return false;
11609
+ }
11610
+ }
11611
+ function failure(error) {
11612
+ return { pid: 0, output: [null, "", ""], stdout: "", stderr: "", status: null, signal: null, error };
11613
+ }
11614
+ function resolveWindowsCommand(command) {
11615
+ const extensions = entries(process.env.PATHEXT ?? DEFAULT_EXTENSIONS);
11616
+ const named2 = extname(command).toLowerCase();
11617
+ const suffixes = named2 !== "" && extensions.some((entry) => entry.toLowerCase() === named2) ? [""] : extensions;
11618
+ const rooted = isAbsolute7(command) || command.includes("/") || command.includes("\\");
11619
+ const directories = rooted ? [""] : entries(process.env.PATH ?? process.env.Path ?? "");
11620
+ for (const directory of directories) {
11621
+ for (const suffix of suffixes) {
11622
+ const path = directory === "" ? `${command}${suffix}` : join5(directory, `${command}${suffix}`);
11623
+ if (present(path)) {
11624
+ return path;
11625
+ }
11626
+ }
11627
+ }
11628
+ return null;
11629
+ }
11630
+ function buildInvocation(executable, args) {
11631
+ if (!SHELL_EXTENSIONS.has(extname(executable).toLowerCase())) {
11632
+ return { file: executable, args: [...args], verbatim: false };
11633
+ }
11634
+ const line2 = [executable, ...args].map((token) => `"${token}"`).join(" ");
11635
+ return { file: process.env.COMSPEC ?? "cmd.exe", args: ["/d", "/s", "/c", `"${line2}"`], verbatim: true };
11636
+ }
11637
+ function runEditorCommand(command, args) {
11638
+ if (process.platform !== "win32") {
11639
+ return spawnSync(command, [...args], SPAWN_OPTIONS);
11640
+ }
11641
+ const executable = resolveWindowsCommand(command);
11642
+ if (executable === null) {
11643
+ return failure(new Error(`The command ${command} was not found on PATH.`));
11644
+ }
11645
+ if ([executable, ...args].some((token) => token.includes('"'))) {
11646
+ return failure(new Error(`The command ${command} received an argument that cannot be quoted safely.`));
11647
+ }
11648
+ const invocation = buildInvocation(executable, args);
11649
+ return spawnSync(invocation.file, invocation.args, { ...SPAWN_OPTIONS, windowsVerbatimArguments: invocation.verbatim });
11650
+ }
11651
+
11652
+ // src/editor/editor-service.ts
11383
11653
  var EXTENSION_ID = "thigasdevelopment.luam";
11384
11654
  var SUPPORTED_EDITORS = [
11385
11655
  { id: "vscode", name: "Visual Studio Code", command: "code" },
@@ -11390,11 +11660,8 @@ var SUPPORTED_EDITORS = [
11390
11660
  ];
11391
11661
  var MAX_VSIX_BYTES = 50 * 1024 * 1024;
11392
11662
  var RELEASE_URL = `https://github.com/ThigasDevelopment/luam/releases/download/v${VERSION}/luam-${VERSION}.vsix`;
11393
- function run(command, args) {
11394
- return spawnSync(command, [...args], { encoding: "utf8", shell: false, windowsHide: true, maxBuffer: 1024 * 1024 });
11395
- }
11396
11663
  function succeeded(result) {
11397
- return result.error === void 0 && result.status === 0;
11664
+ return (result.error === void 0 || result.error === null) && result.status === 0;
11398
11665
  }
11399
11666
  function trustedReleaseHost(url) {
11400
11667
  const host = new URL(url).hostname.toLowerCase();
@@ -11419,11 +11686,11 @@ async function downloadRelease() {
11419
11686
  return bytes;
11420
11687
  }
11421
11688
  function installVsix(editor, bytes) {
11422
- const directory = mkdtempSync(join5(tmpdir(), "luam-"));
11423
- const path = join5(directory, `luam-${VERSION}.vsix`);
11689
+ const directory = mkdtempSync(join6(tmpdir(), "luam-"));
11690
+ const path = join6(directory, `luam-${VERSION}.vsix`);
11424
11691
  try {
11425
11692
  writeFileSync5(path, bytes, { flag: "wx" });
11426
- const result = run(editor.command, ["--install-extension", path, "--force"]);
11693
+ const result = runEditorCommand(editor.command, ["--install-extension", path, "--force"]);
11427
11694
  return succeeded(result) ? { source: "release", error: null } : { source: null, error: "The editor rejected the release VSIX." };
11428
11695
  } finally {
11429
11696
  rmSync(directory, { recursive: true, force: true });
@@ -11432,13 +11699,13 @@ function installVsix(editor, bytes) {
11432
11699
  function createEditorService() {
11433
11700
  let release = null;
11434
11701
  return {
11435
- detect: () => SUPPORTED_EDITORS.filter((editor) => succeeded(run(editor.command, ["--version"]))),
11702
+ detect: () => SUPPORTED_EDITORS.filter((editor) => succeeded(runEditorCommand(editor.command, ["--version"]))),
11436
11703
  hasExtension: (editor) => {
11437
- const result = run(editor.command, ["--list-extensions"]);
11704
+ const result = runEditorCommand(editor.command, ["--list-extensions"]);
11438
11705
  return succeeded(result) && result.stdout.split(/\r?\n/u).some((entry) => entry.trim().toLowerCase() === EXTENSION_ID);
11439
11706
  },
11440
11707
  install: async (editor) => {
11441
- const marketplace = run(editor.command, ["--install-extension", EXTENSION_ID, "--force"]);
11708
+ const marketplace = runEditorCommand(editor.command, ["--install-extension", EXTENSION_ID, "--force"]);
11442
11709
  if (succeeded(marketplace)) {
11443
11710
  return { source: "marketplace", error: null };
11444
11711
  }