@neocompose/cli 0.70.2 → 0.70.4

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/neo.mjs CHANGED
@@ -4658,6 +4658,11 @@ var init_executable_parser = __esm({
4658
4658
  this.expect("operator", "<");
4659
4659
  if (t.text === "Dictionary") {
4660
4660
  const key = this.parseType();
4661
+ if (this.peek().text === ">")
4662
+ throw new CompileError(
4663
+ "Dictionary requires a key type and a value type, for example Dictionary<string, int>.",
4664
+ at(this.peek())
4665
+ );
4661
4666
  this.expect("punctuation", ",");
4662
4667
  const value = this.parseType();
4663
4668
  this.expect("operator", ">");
@@ -5244,8 +5249,16 @@ var init_executable_parser = __esm({
5244
5249
  parseObjectInitializer() {
5245
5250
  this.expect("punctuation", "{");
5246
5251
  const entries = [];
5252
+ const names = /* @__PURE__ */ new Set();
5247
5253
  while (!(this.peek().kind === "punctuation" && this.peek().text === "}")) {
5248
5254
  const name = this.expectMemberName();
5255
+ if (names.has(name.text)) {
5256
+ throw new CompileError(
5257
+ `Initializer assigns '${name.text}' more than once. Remove the duplicate assignment.`,
5258
+ at(name)
5259
+ );
5260
+ }
5261
+ names.add(name.text);
5249
5262
  this.expect("operator", "=");
5250
5263
  entries.push({
5251
5264
  name: name.text,
@@ -5599,6 +5612,8 @@ var init_project_local_values = __esm({
5599
5612
  "LocalizationStatus",
5600
5613
  "ImageRegistry",
5601
5614
  "AudioClipRegistry",
5615
+ "NeoImage",
5616
+ "NeoAudioClip",
5602
5617
  "PriorityGroup",
5603
5618
  "DialogueGroup",
5604
5619
  "Migration"
@@ -9578,11 +9593,13 @@ function collectGlobalChildEntries(sourceText, declaration) {
9578
9593
  if ((token.kind === "type" || token.kind === "identifier") && isProjectNameToken(name) && isValidNeoProjectMemberIdentifier(name.text) && tokens[index + 2]?.text === "=" && tokens[index + 3]?.text !== "=" && // A pending annotation or statement boundary precedes a declaration;
9579
9594
  // `key: value` fields and expression member accesses never do.
9580
9595
  tokens[index - 1]?.text !== "." && tokens[index - 1]?.text !== ":") {
9596
+ const value = tokens[index + 3];
9581
9597
  entries.push({
9582
9598
  typeName: token.text,
9583
9599
  name: name.text,
9584
9600
  nameRange: name.range,
9585
- annotations: pendingAnnotations
9601
+ annotations: pendingAnnotations,
9602
+ ...value?.kind === "identifier" && tokens[index + 4]?.text === ";" ? { reference: value.text } : {}
9586
9603
  });
9587
9604
  pendingAnnotations = [];
9588
9605
  index += 2;
@@ -9907,9 +9924,9 @@ function requiredFirstConstructorParameters(parameters) {
9907
9924
  ...parameters.filter((parameter4) => !parameter4.required)
9908
9925
  ];
9909
9926
  }
9910
- function buildNeoScriptConstructorParameterDescriptors(classId, sources) {
9927
+ function buildNeoScriptConstructorParameterDescriptors(classId, sources2) {
9911
9928
  return requiredFirstConstructorParameters(
9912
- sources.flatMap((source) => {
9929
+ sources2.flatMap((source) => {
9913
9930
  const name = generatedCSharpMemberNameForSchemaKey(source.schemaKey);
9914
9931
  if (!isGeneratedCSharpIdentifier(name)) return [];
9915
9932
  return [
@@ -9930,7 +9947,7 @@ function buildNeoScriptConstructorParameterDescriptors(classId, sources) {
9930
9947
  );
9931
9948
  }
9932
9949
  function buildNeoScriptConstructorSignature(classId, members, memberDescriptor) {
9933
- const sources = members.flatMap((member) => {
9950
+ const sources2 = members.flatMap((member) => {
9934
9951
  if (member.static === true || member.computed === true) return [];
9935
9952
  if (member.kind === "function" || member.kind === "method") return [];
9936
9953
  const descriptor = memberDescriptor(member.id);
@@ -9949,7 +9966,7 @@ function buildNeoScriptConstructorSignature(classId, members, memberDescriptor)
9949
9966
  });
9950
9967
  const parameters = buildNeoScriptConstructorParameterDescriptors(
9951
9968
  classId,
9952
- sources
9969
+ sources2
9953
9970
  ).map((parameter4) => ({
9954
9971
  id: parameter4.id,
9955
9972
  name: parameter4.name,
@@ -10126,6 +10143,52 @@ var init_project = __esm({
10126
10143
  }
10127
10144
  });
10128
10145
 
10146
+ // ../packages/neoscript-language/src/runtime-source.ts
10147
+ function registerRuntimeSource(node, source) {
10148
+ if (!sources.has(node)) sources.set(node, source);
10149
+ }
10150
+ function runtimeSource(node) {
10151
+ let current = node;
10152
+ while (current !== void 0) {
10153
+ const source = sources.get(current);
10154
+ if (source !== void 0) return source;
10155
+ current = origins.get(current);
10156
+ }
10157
+ return void 0;
10158
+ }
10159
+ function cloneWithRuntimeSources(value) {
10160
+ const clone = structuredClone(value);
10161
+ copyRuntimeSources(value, clone);
10162
+ return clone;
10163
+ }
10164
+ function copyRuntimeSources(original, copied) {
10165
+ const pending = [[original, copied]];
10166
+ const seen = /* @__PURE__ */ new WeakSet();
10167
+ while (pending.length > 0) {
10168
+ const [sourceNode, targetNode] = pending.pop();
10169
+ if (typeof sourceNode !== "object" || sourceNode === null || typeof targetNode !== "object" || targetNode === null)
10170
+ continue;
10171
+ if (seen.has(sourceNode)) continue;
10172
+ seen.add(sourceNode);
10173
+ const source = runtimeSource(sourceNode);
10174
+ if (source !== void 0) sources.set(targetNode, source);
10175
+ else if (sourceNode !== targetNode) origins.set(targetNode, sourceNode);
10176
+ for (const key of Object.keys(sourceNode))
10177
+ pending.push([
10178
+ Reflect.get(sourceNode, key),
10179
+ Reflect.get(targetNode, key)
10180
+ ]);
10181
+ }
10182
+ }
10183
+ var sources, origins;
10184
+ var init_runtime_source = __esm({
10185
+ "../packages/neoscript-language/src/runtime-source.ts"() {
10186
+ "use strict";
10187
+ sources = /* @__PURE__ */ new WeakMap();
10188
+ origins = /* @__PURE__ */ new WeakMap();
10189
+ }
10190
+ });
10191
+
10129
10192
  // ../packages/neoscript-language/src/type-substitution.ts
10130
10193
  function substituteTypeParameters(member, receiver, declaring) {
10131
10194
  const parameters = declaring.typeParameters ?? [];
@@ -12518,6 +12581,7 @@ var CONSTRUCTING_FUNCTION_KINDS, VARIANT_INITIALIZE_NAME, VARIANT_APPLY_NAME, MA
12518
12581
  var init_strict_resolver = __esm({
12519
12582
  "../packages/neoscript-language/src/strict-resolver.ts"() {
12520
12583
  "use strict";
12584
+ init_runtime_source();
12521
12585
  init_listener_annotation();
12522
12586
  init_type_substitution();
12523
12587
  init_cell_pattern();
@@ -12665,8 +12729,9 @@ var init_strict_resolver = __esm({
12665
12729
  valueId;
12666
12730
  };
12667
12731
  StrictNeoScriptResolver = class _StrictNeoScriptResolver {
12668
- constructor(context, projectIndex, syntax) {
12732
+ constructor(context, projectIndex, syntax, sourceUri) {
12669
12733
  this.context = context;
12734
+ this.sourceUri = sourceUri;
12670
12735
  this.project = projectIndex ?? createProjectIndex(context.project);
12671
12736
  this.listenerParameters = createListenerAnnotationProvider(
12672
12737
  this.project,
@@ -12710,6 +12775,7 @@ var init_strict_resolver = __esm({
12710
12775
  );
12711
12776
  }
12712
12777
  context;
12778
+ sourceUri;
12713
12779
  project;
12714
12780
  listenerParameters;
12715
12781
  thisVariable;
@@ -13055,6 +13121,20 @@ var init_strict_resolver = __esm({
13055
13121
  }
13056
13122
  }
13057
13123
  resolveStatement(statement, scope) {
13124
+ const instruction = this.resolveStatementCore(statement, scope);
13125
+ if (this.source !== void 0) {
13126
+ const source = {
13127
+ ...statement.pos,
13128
+ text: this.source,
13129
+ uri: this.sourceUri
13130
+ };
13131
+ registerRuntimeSource(instruction, source);
13132
+ if (instruction.type === "functionCall" /* FunctionCall */)
13133
+ registerRuntimeSource(instruction.call, source);
13134
+ }
13135
+ return instruction;
13136
+ }
13137
+ resolveStatementCore(statement, scope) {
13058
13138
  switch (statement.kind) {
13059
13139
  case "varDecl": {
13060
13140
  if (this.valueVariable && statement.name === "value") {
@@ -13706,7 +13786,12 @@ var init_strict_resolver = __esm({
13706
13786
  statement.pos
13707
13787
  );
13708
13788
  }
13709
- this.requireAssignable(value.type, expected, statement.pos, "return");
13789
+ this.requireAssignable(
13790
+ value.type,
13791
+ expected,
13792
+ statement.expr.pos,
13793
+ "return"
13794
+ );
13710
13795
  return {
13711
13796
  type: "return" /* Return */,
13712
13797
  pointer: value.pointer
@@ -14647,6 +14732,16 @@ var init_strict_resolver = __esm({
14647
14732
  }
14648
14733
  }
14649
14734
  resolveExpression(expression, scope, expected) {
14735
+ const resolved = this.resolveExpressionCore(expression, scope, expected);
14736
+ if (this.source !== void 0)
14737
+ registerRuntimeSource(resolved.pointer, {
14738
+ ...expression.pos,
14739
+ text: this.source,
14740
+ uri: this.sourceUri
14741
+ });
14742
+ return resolved;
14743
+ }
14744
+ resolveExpressionCore(expression, scope, expected) {
14650
14745
  if (expected?.kind === "delegate") {
14651
14746
  if (expression.kind === "lambda") {
14652
14747
  return this.resolveDelegateLambda(expression, expected, scope);
@@ -14858,13 +14953,13 @@ var init_strict_resolver = __esm({
14858
14953
  }
14859
14954
  if (expression.typeArguments) {
14860
14955
  throw new CompileError(
14861
- "Generic intrinsic calls require a declaration-aware project source context.",
14956
+ "Type arguments are not supported for this call. Remove the explicit type arguments.",
14862
14957
  expression.pos
14863
14958
  );
14864
14959
  }
14865
14960
  if (expression.argumentNames) {
14866
14961
  throw new CompileError(
14867
- "Named arguments require a declaration-aware project source call context.",
14962
+ "Named arguments are not supported for this call. Pass arguments in declaration order, without names.",
14868
14963
  expression.pos
14869
14964
  );
14870
14965
  }
@@ -18195,7 +18290,7 @@ var init_strict_resolver = __esm({
18195
18290
  this.requireAssignable(
18196
18291
  resolved.type,
18197
18292
  expected,
18198
- pos,
18293
+ argument2.pos,
18199
18294
  `argument ${index + 1}`
18200
18295
  );
18201
18296
  }
@@ -23918,6 +24013,9 @@ function annotationContextForDeclaration(declaration) {
23918
24013
  if (declaration.kind === "global" && isNeoVariantTypeName(declaration.type.name)) {
23919
24014
  return { kind: "variantGlobal", declaration };
23920
24015
  }
24016
+ if (declaration.kind === "global" && (declaration.type.name === "NeoImage" || declaration.type.name === "NeoAudioClip")) {
24017
+ return { kind: "projectFile", typeName: declaration.type.name };
24018
+ }
23921
24019
  return { kind: "schema" };
23922
24020
  }
23923
24021
  function contextualAnnotationFields(document, position) {
@@ -24697,7 +24795,7 @@ function validateAnnotations(uri, annotations, context, environment, diagnostics
24697
24795
  }
24698
24796
  function fieldsForAnnotation(name, context, environment) {
24699
24797
  if (name === "id") {
24700
- return context.kind === "class" || context.kind === "schema" || context.kind === "member" || context.kind === "variantGlobal" || context.kind === "flowMember" || context.kind === "flowBinding" || context.kind === "flowGraph" ? [{ name: "id", type: "string" }] : null;
24798
+ return context.kind === "class" || context.kind === "schema" || context.kind === "member" || context.kind === "variantGlobal" || context.kind === "projectFile" || context.kind === "flowMember" || context.kind === "flowBinding" || context.kind === "flowGraph" ? [{ name: "id", type: "string" }] : null;
24701
24799
  }
24702
24800
  if (name === "primary") return context.kind === "flowBinding" ? [] : null;
24703
24801
  if (name === "incomplete") {
@@ -24735,6 +24833,15 @@ function settingsFields(context) {
24735
24833
  }
24736
24834
  ];
24737
24835
  }
24836
+ if (context.kind === "projectFile") {
24837
+ return [
24838
+ {
24839
+ name: "template",
24840
+ type: context.typeName === "NeoImage" ? "TextureTemplate" : "AudioTemplate"
24841
+ },
24842
+ { name: "name", type: "string" }
24843
+ ];
24844
+ }
24738
24845
  if (context.kind === "class") {
24739
24846
  if (context.declaration.baseTypes.some((type2) => type2.name === "Dialogue")) {
24740
24847
  return [
@@ -25285,7 +25392,7 @@ function validateExpression(expression, expected, scope, environment, uri, range
25285
25392
  pushDiagnostic(
25286
25393
  diagnostics,
25287
25394
  uri,
25288
- range2,
25395
+ anchor ? anchorSpan(anchor, expression.pos, 1) : range2,
25289
25396
  "project-expression-type-mismatch",
25290
25397
  `Expected ${describeType(expected)} but received ${describeType(actual)}.`
25291
25398
  );
@@ -26499,6 +26606,7 @@ function findTypeAnnotation(type, position) {
26499
26606
  function describeAnnotationContext(context) {
26500
26607
  if (context.kind === "member" || context.kind === "type" || context.kind === "flowBinding" || context.kind === "flowMember")
26501
26608
  return `${context.type.name} ${context.kind}`;
26609
+ if (context.kind === "projectFile") return `${context.typeName} declaration`;
26502
26610
  return context.kind;
26503
26611
  }
26504
26612
  function lowerFirst2(value) {
@@ -26706,7 +26814,8 @@ function compileNeoScriptStrict(document, context, options = {}) {
26706
26814
  const emitted = new StrictNeoScriptResolver(
26707
26815
  resolverContext,
26708
26816
  projectIndex,
26709
- syntax.lexed
26817
+ syntax.lexed,
26818
+ document.uri
26710
26819
  ).compile(ast);
26711
26820
  const ir = attachDependencyManifest(
26712
26821
  emitted,
@@ -27942,6 +28051,13 @@ function innerBodyRange(source, range2) {
27942
28051
  }
27943
28052
  function collectRegistryMembers(documents) {
27944
28053
  const byRegistryType = /* @__PURE__ */ new Map();
28054
+ const fileDeclarations = /* @__PURE__ */ new Map();
28055
+ for (const document of documents.values()) {
28056
+ for (const declaration of document.declarations) {
28057
+ if (declaration.kind === "global" && neoScriptRegistryReferenceType(declaration.type.name) !== null)
28058
+ fileDeclarations.set(declaration.name, declaration);
28059
+ }
28060
+ }
27945
28061
  for (const [uri, document] of documents) {
27946
28062
  for (const declaration of document.declarations) {
27947
28063
  if (declaration.kind !== "global") continue;
@@ -27955,9 +28071,10 @@ function collectRegistryMembers(documents) {
27955
28071
  declaration
27956
28072
  )) {
27957
28073
  if (entry.typeName !== registry.entryType) continue;
28074
+ const listed = entry.reference === void 0 ? void 0 : fileDeclarations.get(entry.reference);
27958
28075
  members.push({
27959
28076
  id: sourceIdentity2(
27960
- entry.annotations,
28077
+ listed?.type.name === registry.entryType ? listed.annotations : entry.annotations,
27961
28078
  `${declaration.type.name}:${uri}:${entry.name}`
27962
28079
  ),
27963
28080
  name: entry.name,
@@ -30825,6 +30942,7 @@ function compileNeoProjectSources(inputs) {
30825
30942
  const schemaEnums = [];
30826
30943
  const configuration = [];
30827
30944
  const files = [];
30945
+ const fileDeclarations = [];
30828
30946
  const values = [];
30829
30947
  const dialogueGroups = [];
30830
30948
  const variants = [];
@@ -30863,6 +30981,8 @@ function compileNeoProjectSources(inputs) {
30863
30981
  configuration.push(global);
30864
30982
  } else if (FILE_REGISTRY_TYPES.has(declaration.type.name)) {
30865
30983
  files.push(global);
30984
+ } else if (FILE_DECLARATION_TYPES.has(declaration.type.name)) {
30985
+ fileDeclarations.push(global);
30866
30986
  } else if (GROUP_TYPES.has(declaration.type.name)) {
30867
30987
  dialogueGroups.push(global);
30868
30988
  } else if (isNeoVariantFamilyTypeName(declaration.type.name)) {
@@ -30885,7 +31005,7 @@ function compileNeoProjectSources(inputs) {
30885
31005
  },
30886
31006
  relations: { globals: relations },
30887
31007
  configuration: { globals: configuration },
30888
- files: { registries: files },
31008
+ files: { registries: files, declarations: fileDeclarations },
30889
31009
  values: { globals: values },
30890
31010
  dialogueGroups: { globals: dialogueGroups },
30891
31011
  variants: { globals: variants },
@@ -31013,6 +31133,7 @@ function memberManifest(uri, member) {
31013
31133
  function globalManifest(uri, declaration) {
31014
31134
  return {
31015
31135
  ...identity(uri, declaration.range, declaration.annotations),
31136
+ initializerSource: { uri, range: declaration.initializer.range },
31016
31137
  name: declaration.name,
31017
31138
  type: manifestType(declaration.type),
31018
31139
  annotations: manifestAnnotations(declaration.annotations),
@@ -31191,12 +31312,13 @@ function assertProjectSourceAnalysisV4(value) {
31191
31312
  "globals",
31192
31313
  assertGlobal
31193
31314
  );
31194
- assertArrayEnvelope(
31195
- analysis.files,
31196
- "analysis.files",
31197
- "registries",
31198
- assertGlobal
31199
- );
31315
+ const files = record(analysis.files, "analysis.files");
31316
+ exactKeys(files, "analysis.files", ["registries", "declarations"]);
31317
+ for (const field of ["registries", "declarations"]) {
31318
+ array(files[field], `analysis.files.${field}`).forEach(
31319
+ (entry, index) => assertGlobal(entry, `analysis.files.${field}[${index}]`)
31320
+ );
31321
+ }
31200
31322
  assertArrayEnvelope(
31201
31323
  analysis.values,
31202
31324
  "analysis.values",
@@ -31492,16 +31614,15 @@ function assertEnum(value, path) {
31492
31614
  }
31493
31615
  function assertGlobal(value, path) {
31494
31616
  const global = record(value, path);
31495
- exactKeys(global, path, [
31496
- "id",
31497
- "state",
31498
- "source",
31499
- "name",
31500
- "type",
31501
- "annotations",
31502
- "initializer"
31503
- ]);
31617
+ exactKeysWithOptional(
31618
+ global,
31619
+ path,
31620
+ ["id", "state", "source", "name", "type", "annotations", "initializer"],
31621
+ ["initializerSource"]
31622
+ );
31504
31623
  assertIdentity(global, path);
31624
+ if (global.initializerSource !== void 0)
31625
+ assertLocation(global.initializerSource, `${path}.initializerSource`);
31505
31626
  string(global.name, `${path}.name`);
31506
31627
  assertType(global.type, `${path}.type`);
31507
31628
  array(global.annotations, `${path}.annotations`).forEach(
@@ -31783,7 +31904,7 @@ function string(value, path) {
31783
31904
  }
31784
31905
  return value;
31785
31906
  }
31786
- var CONFIGURATION_TYPES, FILE_REGISTRY_TYPES, GROUP_TYPES, MIGRATION_TYPES;
31907
+ var CONFIGURATION_TYPES, FILE_REGISTRY_TYPES, FILE_DECLARATION_TYPES, GROUP_TYPES, MIGRATION_TYPES;
31787
31908
  var init_project_source_manifest = __esm({
31788
31909
  "../packages/neoscript-language/src/project-source-manifest.ts"() {
31789
31910
  "use strict";
@@ -31800,6 +31921,7 @@ var init_project_source_manifest = __esm({
31800
31921
  "LocalizationStatus"
31801
31922
  ]);
31802
31923
  FILE_REGISTRY_TYPES = /* @__PURE__ */ new Set(["ImageRegistry", "AudioClipRegistry"]);
31924
+ FILE_DECLARATION_TYPES = /* @__PURE__ */ new Set(["NeoImage", "NeoAudioClip"]);
31803
31925
  GROUP_TYPES = /* @__PURE__ */ new Set(["PriorityGroup", "DialogueGroup"]);
31804
31926
  MIGRATION_TYPES = /* @__PURE__ */ new Set(["Migration"]);
31805
31927
  }
@@ -31811,7 +31933,7 @@ var init_registry = __esm({
31811
31933
  "../packages/manifest/schema-contract/registry.mjs"() {
31812
31934
  "use strict";
31813
31935
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
31814
- cliVersion: "0.70.2",
31936
+ cliVersion: "0.70.4",
31815
31937
  projectFileUploadBatchSize: 32,
31816
31938
  documentRecords: {
31817
31939
  member: {
@@ -54583,10 +54705,10 @@ ${schemaClass2.genericParameters.map((parameter4) => {
54583
54705
  return { member, source };
54584
54706
  });
54585
54707
  const constructorIds = schemaClass2.constructorIds ?? [];
54586
- const sources = rendered.map((entry) => entry.source);
54708
+ const sources2 = rendered.map((entry) => entry.source);
54587
54709
  for (const memberId of memberIds)
54588
54710
  for (const statement of listenerStatementsByMember?.get(memberId) ?? [])
54589
- sources.push(indentNeoSourceNonEmptyLines(statement, 2));
54711
+ sources2.push(indentNeoSourceNonEmptyLines(statement, 2));
54590
54712
  const spliced = [
54591
54713
  ...constructorIds.map(
54592
54714
  (constructorId) => emitConstructor(
@@ -54600,7 +54722,7 @@ ${schemaClass2.genericParameters.map((parameter4) => {
54600
54722
  if (spliced.length === 0) {
54601
54723
  return `${emitDocsText(schemaClass2.docsText)}${annotations.join("\n")}
54602
54724
  ${header}
54603
- ${sources.join("\n\n")}
54725
+ ${sources2.join("\n\n")}
54604
54726
  }
54605
54727
  `;
54606
54728
  }
@@ -54609,9 +54731,9 @@ ${sources.join("\n\n")}
54609
54731
  -1
54610
54732
  );
54611
54733
  const members = [
54612
- ...sources.slice(0, lastValueMemberIndex + 1),
54734
+ ...sources2.slice(0, lastValueMemberIndex + 1),
54613
54735
  ...spliced.map((source) => indentNeoSourceNonEmptyLines(source, 2)),
54614
- ...sources.slice(lastValueMemberIndex + 1)
54736
+ ...sources2.slice(lastValueMemberIndex + 1)
54615
54737
  ];
54616
54738
  return `${emitDocsText(schemaClass2.docsText)}${annotations.join("\n")}
54617
54739
  ${header}
@@ -60587,8 +60709,9 @@ function validateProjectFileDeclarationSymbolsV4(declarations) {
60587
60709
  for (const declaration of declarations) {
60588
60710
  const expected = expectedSymbols.get(declaration.recordId);
60589
60711
  if (expected === void 0 || declaration.symbol === expected) continue;
60590
- throw new Error(
60591
- `Project file ${declaration.symbol} must use registry symbol ${expected}, derived from its persisted name ${JSON.stringify(declaration.persistedName)}. Rename the declaration, or set @settings(name: ...) to a file name ending in ${JSON.stringify(sourceExtension(declaration.persistedName))} that derives ${declaration.symbol}.`
60712
+ throw projectFileSourceError(
60713
+ `Project file ${declaration.symbol} must use registry symbol ${expected}, derived from its persisted name ${JSON.stringify(declaration.persistedName)}. Rename the declaration, or set @settings(name: ...) to a file name ending in ${JSON.stringify(sourceExtension(declaration.persistedName))} that derives ${declaration.symbol}.`,
60714
+ declaration
60592
60715
  );
60593
60716
  }
60594
60717
  }
@@ -60606,7 +60729,24 @@ function projectFileUnityImportSettingsV4(kind, templateId) {
60606
60729
  values: {}
60607
60730
  };
60608
60731
  }
60609
- function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ */ new Map()) {
60732
+ function projectFileSourceError(error, declaration) {
60733
+ if (error instanceof SchemaSourceError) return error;
60734
+ return new SchemaSourceError(
60735
+ error instanceof Error ? error.message : String(error),
60736
+ declaration.sourceFile,
60737
+ declaration.sourceLine,
60738
+ declaration.sourceColumn ?? 1
60739
+ );
60740
+ }
60741
+ function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ */ new Map(), placements = []) {
60742
+ const listed = /* @__PURE__ */ new Map();
60743
+ for (const placement of placements) {
60744
+ const fileId = placement.valueIds[0];
60745
+ if (placement.type !== "NeoImage" && placement.type !== "NeoAudioClip")
60746
+ continue;
60747
+ if (fileId !== void 0) listed.set(fileId, placement);
60748
+ }
60749
+ const declarationFiles = [];
60610
60750
  const liveFiles = [...records2.values()].filter((record6) => record6.recordKind === "project-file" && !record6.deleted).map((record6) => ({ record: record6, data: requireFileData(record6) }));
60611
60751
  assertUniqueProjectBinaryPaths(liveFiles);
60612
60752
  const symbols = assignDeterministicFileSymbols(
@@ -60617,10 +60757,11 @@ function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ *
60617
60757
  );
60618
60758
  const textureTemplates = templateNames(records2, "unity-texture-template");
60619
60759
  const audioTemplates = templateNames(records2, "unity-audio-clip-template");
60620
- return ["image", "audio"].map((kind) => {
60760
+ const registries2 = ["image", "audio"].map((kind) => {
60621
60761
  const registryType = kind === "image" ? "ImageRegistry" : "AudioClipRegistry";
60622
60762
  const registryName = kind === "image" ? "Images" : "AudioClips";
60623
60763
  const valueType = kind === "image" ? "NeoImage" : "NeoAudioClip";
60764
+ const registryKeys = [];
60624
60765
  const entries = liveFiles.filter(({ data }) => data.fileType === kind).sort((left, right) => {
60625
60766
  const leftSymbol = symbols.get(left.record.recordId) ?? "";
60626
60767
  const rightSymbol = symbols.get(right.record.recordId) ?? "";
@@ -60645,10 +60786,22 @@ function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ *
60645
60786
  ...templateName ? [`template: ${templateName}`] : [],
60646
60787
  ...data.name === sourceBasename(path) ? [] : [`name: ${JSON.stringify(data.name)}`]
60647
60788
  ];
60648
- const annotation2 = options.length === 0 ? "" : ` @settings(${options.join(", ")})
60649
- `;
60650
- return ` @id(${JSON.stringify(record6.recordId)})
60651
- ${annotation2} ${valueType} ${symbol} = new(${JSON.stringify(path)});`;
60789
+ const declare = (name, indent2) => `${indent2}@id(${JSON.stringify(record6.recordId)})
60790
+ ${options.length === 0 ? "" : `${indent2}@settings(${options.join(", ")})
60791
+ `}${indent2}${valueType} ${name} = new(${JSON.stringify(path)});`;
60792
+ const key = `project-file:${record6.recordId}`;
60793
+ const placement = listed.get(record6.recordId);
60794
+ if (placement?.type === valueType) {
60795
+ declarationFiles.push({
60796
+ path: placement.file,
60797
+ content: `${declare(placement.name, "")}
60798
+ `,
60799
+ recordKeys: [key]
60800
+ });
60801
+ return ` ${valueType} ${symbol} = ${placement.name};`;
60802
+ }
60803
+ registryKeys.push(key);
60804
+ return declare(symbol, " ");
60652
60805
  });
60653
60806
  return {
60654
60807
  path: `Files/${registryName}.neo`,
@@ -60657,14 +60810,42 @@ ${entries.join("\n\n")}
60657
60810
  `}
60658
60811
  }
60659
60812
  `,
60660
- recordKeys: liveFiles.filter(({ data }) => data.fileType === kind).map(({ record: record6 }) => `project-file:${record6.recordId}`)
60813
+ recordKeys: registryKeys
60661
60814
  };
60662
60815
  });
60816
+ return [...registries2, ...declarationFiles];
60663
60817
  }
60664
60818
  function projectFileDeclarationsV4(analysis) {
60665
60819
  const templateIds = fileTemplateIdsByName(analysis);
60666
- return analysis.files.registries.flatMap(
60667
- (registry) => parseRegistry(registry, templateIds)
60820
+ const globals = new Map(
60821
+ analysis.files.declarations.map((global) => [global.name, global])
60822
+ );
60823
+ const listed = /* @__PURE__ */ new Set();
60824
+ const declarations = analysis.files.registries.flatMap(
60825
+ (registry) => parseRegistry(registry, templateIds, globals, listed)
60826
+ );
60827
+ for (const global of globals.values()) {
60828
+ if (listed.has(global.name)) continue;
60829
+ const registry = global.type.name === "NeoImage" ? "Images" : "AudioClips";
60830
+ throw new SchemaSourceError(
60831
+ `${global.type.name} ${global.name} in ${global.source.uri} is not listed in ${registry}. Add "${global.type.name} <Symbol> = ${global.name};" to Files/${registry}.neo.`,
60832
+ global.source.uri,
60833
+ global.source.range.start.line + 1,
60834
+ global.source.range.start.character + 1
60835
+ );
60836
+ }
60837
+ return declarations;
60838
+ }
60839
+ function projectFilePlacementsV4(analysis) {
60840
+ return projectFileDeclarationsV4(analysis).flatMap(
60841
+ (declaration) => declaration.global === void 0 ? [] : [
60842
+ {
60843
+ name: declaration.global,
60844
+ type: declaration.kind === "image" ? "NeoImage" : "NeoAudioClip",
60845
+ file: declaration.sourceFile,
60846
+ valueIds: [declaration.recordId]
60847
+ }
60848
+ ]
60668
60849
  );
60669
60850
  }
60670
60851
  function lowerTrustedProjectFileRegistrySourcesV4(state, analysis, trustedPendingFiles = /* @__PURE__ */ new Map()) {
@@ -60673,73 +60854,77 @@ function lowerTrustedProjectFileRegistrySourcesV4(state, analysis, trustedPendin
60673
60854
  const seenPaths = /* @__PURE__ */ new Map();
60674
60855
  const seenSymbols = /* @__PURE__ */ new Map();
60675
60856
  return declarations.map((declaration) => {
60676
- const normalizedPath = normalizeTrustedProjectBinaryPath(
60677
- declaration.path,
60678
- declaration.kind
60679
- );
60680
- const pathKey = normalizedPath.toLowerCase();
60681
- const previousPath = seenPaths.get(pathKey);
60682
- if (previousPath) {
60683
- throw new Error(
60684
- `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
60685
- );
60686
- }
60687
- seenPaths.set(pathKey, declaration);
60688
- const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
60689
- if (seenSymbols.has(symbolKey)) {
60690
- throw new Error(
60691
- `Project file symbol ${declaration.symbol} is declared more than once.`
60692
- );
60693
- }
60694
- seenSymbols.set(symbolKey, declaration);
60695
- const baseState = state[`project-file:${declaration.recordId}`];
60696
- const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
60697
- const trustedPending = trustedPendingFiles.get(declaration.recordId);
60698
- if (base === null && trustedPending === void 0) {
60699
- throw new Error(
60700
- `Pending project file ${declaration.symbol} has no server-verified staged bytes.`
60701
- );
60702
- }
60703
- const now = Date.now();
60704
- const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
60705
- const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
60706
- const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
60707
- const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString(baseSettings?.templateId) !== declaration.templateId;
60708
- const next = {
60709
- ...base ?? {},
60710
- id: declaration.recordId,
60711
- name: declaration.persistedName,
60712
- fileType: declaration.kind,
60713
- ...base === null && trustedPending !== void 0 ? {
60714
- mimeType: trustedPending.mimeType,
60715
- byteLength: trustedPending.byteLength,
60716
- contentSha256: trustedPending.sha256
60717
- } : {},
60718
- [otherSettingsField]: null
60719
- };
60720
- if (base === null) {
60721
- Object.assign(next, {
60722
- projectId: "",
60723
- status: "pending-upload",
60724
- storageKey: "",
60725
- storageETag: null,
60726
- createdAt: now,
60727
- updatedAt: now
60728
- });
60729
- }
60730
- if (importSettingsChanged) {
60731
- next[settingsField] = projectFileUnityImportSettingsV4(
60732
- declaration.kind,
60733
- declaration.templateId
60857
+ try {
60858
+ const normalizedPath = normalizeTrustedProjectBinaryPath(
60859
+ declaration.path,
60860
+ declaration.kind
60734
60861
  );
60862
+ const pathKey = normalizedPath.toLowerCase();
60863
+ const previousPath = seenPaths.get(pathKey);
60864
+ if (previousPath) {
60865
+ throw new Error(
60866
+ `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
60867
+ );
60868
+ }
60869
+ seenPaths.set(pathKey, declaration);
60870
+ const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
60871
+ if (seenSymbols.has(symbolKey)) {
60872
+ throw new Error(
60873
+ `Project file symbol ${declaration.symbol} is declared more than once.`
60874
+ );
60875
+ }
60876
+ seenSymbols.set(symbolKey, declaration);
60877
+ const baseState = state[`project-file:${declaration.recordId}`];
60878
+ const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
60879
+ const trustedPending = trustedPendingFiles.get(declaration.recordId);
60880
+ if (base === null && trustedPending === void 0) {
60881
+ throw new Error(
60882
+ `Pending project file ${declaration.symbol} has no server-verified staged bytes.`
60883
+ );
60884
+ }
60885
+ const now = Date.now();
60886
+ const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
60887
+ const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
60888
+ const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
60889
+ const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString(baseSettings?.templateId) !== declaration.templateId;
60890
+ const next = {
60891
+ ...base ?? {},
60892
+ id: declaration.recordId,
60893
+ name: declaration.persistedName,
60894
+ fileType: declaration.kind,
60895
+ ...base === null && trustedPending !== void 0 ? {
60896
+ mimeType: trustedPending.mimeType,
60897
+ byteLength: trustedPending.byteLength,
60898
+ contentSha256: trustedPending.sha256
60899
+ } : {},
60900
+ [otherSettingsField]: null
60901
+ };
60902
+ if (base === null) {
60903
+ Object.assign(next, {
60904
+ projectId: "",
60905
+ status: "pending-upload",
60906
+ storageKey: "",
60907
+ storageETag: null,
60908
+ createdAt: now,
60909
+ updatedAt: now
60910
+ });
60911
+ }
60912
+ if (importSettingsChanged) {
60913
+ next[settingsField] = projectFileUnityImportSettingsV4(
60914
+ declaration.kind,
60915
+ declaration.templateId
60916
+ );
60917
+ }
60918
+ return {
60919
+ recordKind: "project-file",
60920
+ recordId: declaration.recordId,
60921
+ fileFields: next,
60922
+ file: declaration.sourceFile,
60923
+ line: declaration.sourceLine
60924
+ };
60925
+ } catch (error) {
60926
+ throw projectFileSourceError(error, declaration);
60735
60927
  }
60736
- return {
60737
- recordKind: "project-file",
60738
- recordId: declaration.recordId,
60739
- fileFields: next,
60740
- file: declaration.sourceFile,
60741
- line: declaration.sourceLine
60742
- };
60743
60928
  });
60744
60929
  }
60745
60930
  function validateFinalizedFileAssignmentsV4(records2, options = {}) {
@@ -60862,7 +61047,7 @@ function fileTemplateIdsByName(analysis) {
60862
61047
  }
60863
61048
  return templateIds;
60864
61049
  }
60865
- function parseRegistry(registry, templateIds) {
61050
+ function parseRegistry(registry, templateIds, globals, listed) {
60866
61051
  const kind = registry.type.name === "ImageRegistry" ? "image" : registry.type.name === "AudioClipRegistry" ? "audio" : null;
60867
61052
  if (kind === null) {
60868
61053
  throw new Error(
@@ -60875,53 +61060,150 @@ function parseRegistry(registry, templateIds) {
60875
61060
  `${registry.type.name} must use the canonical global name ${expectedName}.`
60876
61061
  );
60877
61062
  }
60878
- const body = objectInitializerBody(registry.initializer, registry.type.name);
60879
- const result = [];
60880
- let cursor = 0;
60881
- while (cursor < body.length) {
60882
- const whitespace = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/.exec(
60883
- body.slice(cursor)
60884
- );
60885
- if (whitespace) cursor += whitespace[0].length;
60886
- if (cursor >= body.length) break;
60887
- REGISTRY_ENTRY.lastIndex = cursor;
60888
- const match = REGISTRY_ENTRY.exec(body);
60889
- if (!match) {
60890
- throw new Error(
60891
- `${registry.type.name} ${registry.name} contains an invalid file declaration near ${JSON.stringify(body.slice(cursor, cursor + 80))}.`
60892
- );
60893
- }
60894
- const entryType = match[3];
60895
- if (kind === "image" && entryType !== "NeoImage" || kind === "audio" && entryType !== "NeoAudioClip") {
60896
- throw new Error(
60897
- `${registry.type.name} cannot contain ${entryType} declarations.`
60898
- );
60899
- }
60900
- const settings = parseFileSettings(match[2], templateIds);
60901
- const path = JSON.parse(match[5]);
60902
- const recordId = match[1] ? JSON.parse(match[1]) : `__pending__:project-file:${encodeURIComponent(registry.source.uri)}:${registry.source.range.start.line}:${encodeURIComponent(match[4])}`;
60903
- const fileName2 = sourceBasename(path);
60904
- if (settings.name !== null) {
60905
- const extension = sourceExtension(fileName2);
60906
- if (!settings.name.toLowerCase().endsWith(extension.toLowerCase())) {
61063
+ const source = lex(registry.initializer).source;
61064
+ const origin = registry.initializerSource?.range.start ?? registry.source.range.start;
61065
+ let errorOffset = 0;
61066
+ let declarationSource;
61067
+ const positioned = (offset) => {
61068
+ const point = source.positionAt(offset);
61069
+ return {
61070
+ sourceFile: registry.source.uri,
61071
+ sourceLine: origin.line + point.line + 1,
61072
+ sourceColumn: point.character + 1 + (point.line === 0 ? origin.character : 0)
61073
+ };
61074
+ };
61075
+ try {
61076
+ const body = objectInitializerBody(
61077
+ registry.initializer,
61078
+ registry.type.name
61079
+ );
61080
+ const result = [];
61081
+ let cursor = 0;
61082
+ while (cursor < body.length) {
61083
+ const whitespace = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/.exec(
61084
+ body.slice(cursor)
61085
+ );
61086
+ if (whitespace) cursor += whitespace[0].length;
61087
+ if (cursor >= body.length) break;
61088
+ declarationSource = void 0;
61089
+ errorOffset = registry.initializer.indexOf("{") + 1 + cursor;
61090
+ REGISTRY_ENTRY.lastIndex = cursor;
61091
+ const match = REGISTRY_ENTRY.exec(body);
61092
+ if (!match) {
60907
61093
  throw new Error(
60908
- `Project file ${match[4]} @settings(name: ${JSON.stringify(settings.name)}) must end with ${JSON.stringify(extension)}, the extension of ${JSON.stringify(path)}.`
61094
+ `${registry.type.name} ${registry.name} contains an invalid file declaration near ${JSON.stringify(body.slice(cursor, cursor + 80))}.`
60909
61095
  );
60910
61096
  }
61097
+ const bodyOffset = registry.initializer.indexOf("{") + 1;
61098
+ const entryOffset = bodyOffset + (match.indices?.[3]?.[0] ?? cursor);
61099
+ errorOffset = entryOffset;
61100
+ const entryType = match[3];
61101
+ if (kind === "image" && entryType !== "NeoImage" || kind === "audio" && entryType !== "NeoAudioClip") {
61102
+ throw new Error(
61103
+ `${registry.type.name} cannot contain ${entryType} declarations.`
61104
+ );
61105
+ }
61106
+ errorOffset = bodyOffset + (match.indices?.[2]?.[0] ?? match.indices?.[3]?.[0] ?? cursor);
61107
+ const entry = match[6] === void 0 ? {
61108
+ id: match[1] === void 0 ? null : JSON.parse(match[1]),
61109
+ settings: match[2],
61110
+ pathLiteral: match[5],
61111
+ name: match[4],
61112
+ source: registry.source
61113
+ } : listedFileDeclaration(match, entryType, globals, listed);
61114
+ if (match[6] !== void 0) declarationSource = entry.source;
61115
+ const settings = parseFileSettings(entry.settings, templateIds);
61116
+ const path = JSON.parse(entry.pathLiteral);
61117
+ const recordId = entry.id ?? `__pending__:project-file:${encodeURIComponent(entry.source.uri)}:${entry.source.range.start.line}:${encodeURIComponent(entry.name)}`;
61118
+ const fileName2 = sourceBasename(path);
61119
+ if (settings.name !== null) {
61120
+ const extension = sourceExtension(fileName2);
61121
+ if (!settings.name.toLowerCase().endsWith(extension.toLowerCase())) {
61122
+ throw new Error(
61123
+ `Project file ${match[4]} @settings(name: ${JSON.stringify(settings.name)}) must end with ${JSON.stringify(extension)}, the extension of ${JSON.stringify(path)}.`
61124
+ );
61125
+ }
61126
+ }
61127
+ result.push({
61128
+ recordId,
61129
+ symbol: match[4],
61130
+ path,
61131
+ kind,
61132
+ templateId: settings.templateId,
61133
+ persistedName: settings.name ?? fileName2,
61134
+ ...match[6] === void 0 ? positioned(entryOffset) : {
61135
+ sourceFile: entry.source.uri,
61136
+ sourceLine: entry.source.range.start.line + 1,
61137
+ sourceColumn: entry.source.range.start.character + 1,
61138
+ global: match[6]
61139
+ }
61140
+ });
61141
+ cursor = REGISTRY_ENTRY.lastIndex;
60911
61142
  }
60912
- result.push({
60913
- recordId,
60914
- symbol: match[4],
60915
- path,
60916
- kind,
60917
- templateId: settings.templateId,
60918
- persistedName: settings.name ?? fileName2,
60919
- sourceFile: registry.source.uri,
60920
- sourceLine: registry.source.range.start.line + 1
60921
- });
60922
- cursor = REGISTRY_ENTRY.lastIndex;
61143
+ return result;
61144
+ } catch (error) {
61145
+ throw projectFileSourceError(
61146
+ error,
61147
+ declarationSource === void 0 ? positioned(errorOffset) : {
61148
+ sourceFile: declarationSource.uri,
61149
+ sourceLine: declarationSource.range.start.line + 1,
61150
+ sourceColumn: declarationSource.range.start.character + 1
61151
+ }
61152
+ );
60923
61153
  }
60924
- return result;
61154
+ }
61155
+ function listedFileDeclaration(match, entryType, globals, listed) {
61156
+ const symbol = match[4];
61157
+ const name = match[6];
61158
+ if (match[1] !== void 0) {
61159
+ throw new Error(
61160
+ `Project file ${symbol} lists ${name}, so its @id belongs on the ${name} declaration.`
61161
+ );
61162
+ }
61163
+ if (match[2] !== void 0) {
61164
+ throw new Error(
61165
+ `Project file ${symbol} lists ${name}, so its @settings belong on the ${name} declaration.`
61166
+ );
61167
+ }
61168
+ const global = globals.get(name);
61169
+ if (global === void 0) {
61170
+ throw new Error(
61171
+ `Project file ${symbol} lists ${name}, which is not a top-level NeoImage or NeoAudioClip declaration.`
61172
+ );
61173
+ }
61174
+ if (global.type.name !== entryType) {
61175
+ throw new Error(
61176
+ `Project file ${symbol} is a ${entryType}, but ${name} is a ${global.type.name}.`
61177
+ );
61178
+ }
61179
+ if (listed.has(name)) {
61180
+ throw new Error(`${global.type.name} ${name} is listed more than once.`);
61181
+ }
61182
+ listed.add(name);
61183
+ const initializer = FILE_DECLARATION_INITIALIZER.exec(
61184
+ global.initializer.trim()
61185
+ );
61186
+ if (initializer === null) {
61187
+ const source = global.initializerSource ?? global.source;
61188
+ throw new SchemaSourceError(
61189
+ `${global.type.name} ${name} must be initialized with new("<path>").`,
61190
+ source.uri,
61191
+ source.range.start.line + 1,
61192
+ source.range.start.character + 1
61193
+ );
61194
+ }
61195
+ const settings = global.annotations.find(
61196
+ (annotation2) => annotation2.name === "settings"
61197
+ );
61198
+ return {
61199
+ id: global.id,
61200
+ settings: settings?.arguments.map(
61201
+ (argument2) => argument2.name === void 0 ? argument2.expression : `${argument2.name}: ${argument2.expression}`
61202
+ ).join(", "),
61203
+ pathLiteral: initializer[1],
61204
+ name,
61205
+ source: global.source
61206
+ };
60925
61207
  }
60926
61208
  function parseFileSettings(source, templateIds) {
60927
61209
  if (!source) return { templateId: null, name: null };
@@ -61028,7 +61310,7 @@ function normalizeTrustedProjectBinaryPath(path, kind) {
61028
61310
  throw new Error("Project file path contains a null byte.");
61029
61311
  if (normalized.split("/").some((part) => part === ".." || part === "." || part === "")) {
61030
61312
  throw new Error(
61031
- `Project file path ${JSON.stringify(path)} contains an invalid path segment.`
61313
+ `Project file path ${JSON.stringify(path)} contains an invalid path segment. Use a path relative to neo.json without '..', '.', or empty segments.`
61032
61314
  );
61033
61315
  }
61034
61316
  if (neoProjectPathHasIgnoredDirectory(normalized)) {
@@ -61045,10 +61327,12 @@ function normalizeTrustedProjectBinaryPath(path, kind) {
61045
61327
  }
61046
61328
  return normalized;
61047
61329
  }
61048
- var REGISTRY_ENTRY, assignDeterministicFileSymbols;
61330
+ var REGISTRY_ENTRY, FILE_DECLARATION_INITIALIZER, assignDeterministicFileSymbols;
61049
61331
  var init_project_file_source = __esm({
61050
61332
  "src/project-source/project-file-source.ts"() {
61051
61333
  "use strict";
61334
+ init_lexer();
61335
+ init_source_diagnostics();
61052
61336
  init_executable_parser();
61053
61337
  init_language_spec();
61054
61338
  init_value_comparison();
@@ -61056,7 +61340,8 @@ var init_project_file_source = __esm({
61056
61340
  init_project_file_registry();
61057
61341
  init_members();
61058
61342
  init_unity_import_settings();
61059
- REGISTRY_ENTRY = /(?:@id\(\s*("(?:[^"\\]|\\.)*")\s*\)\s*)?(?:@settings\(([\s\S]*?)\)\s*)?(NeoImage|NeoAudioClip)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*new\(\s*("(?:[^"\\]|\\.)*")\s*\)\s*;/gy;
61343
+ REGISTRY_ENTRY = /(?:@id\(\s*("(?:[^"\\]|\\.)*")\s*\)\s*)?(?:@settings\(([\s\S]*?)\)\s*)?(NeoImage|NeoAudioClip)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:new\(\s*("(?:[^"\\]|\\.)*")\s*\)|([A-Za-z_][A-Za-z0-9_]*))\s*;/dgy;
61344
+ FILE_DECLARATION_INITIALIZER = /^new\(\s*("(?:[^"\\]|\\.)*")\s*\)$/;
61060
61345
  assignDeterministicFileSymbols = assignDeterministicProjectFileSymbols;
61061
61346
  }
61062
61347
  });
@@ -61078,7 +61363,13 @@ function emitSupplementalProjectSourcesV4(records2, binaryPaths, options = {}) {
61078
61363
  options.manifest?.members.map((entry) => [entry.id, entry])
61079
61364
  )
61080
61365
  };
61081
- files.push(...emitProjectFileRegistrySourcesV4(records2, binaryPaths));
61366
+ files.push(
61367
+ ...emitProjectFileRegistrySourcesV4(
61368
+ records2,
61369
+ binaryPaths,
61370
+ options.localValues
61371
+ )
61372
+ );
61082
61373
  const project = live.find((record6) => record6.recordKind === "project");
61083
61374
  if (project) {
61084
61375
  const data = recordData(project);
@@ -61487,8 +61778,12 @@ function lowerDialogueGroup(baseValue, global, priorityIds, dialogueIds, uiLogic
61487
61778
  const path = `${collection.receiver.name}.${collection.name}`;
61488
61779
  collectionMemberId = resolveQualifiedMemberId(memberContext, path);
61489
61780
  if (collectionMemberId === null) {
61490
- throw new Error(
61491
- `Lookup dialogue group ${global.name} names unknown collection ${path}.`
61781
+ const origin = global.initializerSource ?? global.source;
61782
+ throw new SchemaSourceError(
61783
+ `Lookup dialogue group ${global.name} names unknown collection ${path}. Use an existing collection member.`,
61784
+ origin.uri,
61785
+ origin.range.start.line + collection.pos.line,
61786
+ collection.pos.column + (collection.pos.line === 1 ? origin.range.start.character : 0)
61492
61787
  );
61493
61788
  }
61494
61789
  } else {
@@ -62078,6 +62373,7 @@ var PRIORITY_OPTION_PATTERN;
62078
62373
  var init_supplemental_records = __esm({
62079
62374
  "src/project-source/supplemental-records.ts"() {
62080
62375
  "use strict";
62376
+ init_source_diagnostics();
62081
62377
  init_project_source_identifiers();
62082
62378
  init_lexer();
62083
62379
  init_executable_parser();
@@ -65952,14 +66248,14 @@ function createNeoScriptProjectInScope(context) {
65952
66248
  if (context.dialogueContext) {
65953
66249
  languageTypes.push(createDialogueContextType(context));
65954
66250
  }
65955
- const registries = projectFileRegistryProjection(
66251
+ const registries2 = projectFileRegistryProjection(
65956
66252
  context.vm.projectFiles ?? []
65957
66253
  );
65958
- languageTypes.push(...registries.types);
66254
+ languageTypes.push(...registries2.types);
65959
66255
  return {
65960
66256
  version: projectLanguageVersion(context),
65961
66257
  types: languageTypes,
65962
- globals: registries.globals,
66258
+ globals: registries2.globals,
65963
66259
  roots: [
65964
66260
  {
65965
66261
  id: "neoscript:root",
@@ -67313,10 +67609,33 @@ var NSGetterRuntimeError, UncompiledInitializerRuntimeError;
67313
67609
  var init_NSGetterRuntimeError = __esm({
67314
67610
  "../src/runtime/neoscript/NSGetterRuntimeError.ts"() {
67315
67611
  "use strict";
67612
+ init_runtime_source();
67316
67613
  NSGetterRuntimeError = class extends Error {
67614
+ /** Keep catch-visible runtime messages stable; add guidance for diagnostic hosts. */
67615
+ get diagnosticMessage() {
67616
+ if (this.message === "Division by zero")
67617
+ return `${this.message}. Check that the divisor is nonzero before dividing.`;
67618
+ if (this.message === "Modulo by zero")
67619
+ return `${this.message}. Check that the divisor is nonzero before taking the remainder.`;
67620
+ return this.message;
67621
+ }
67622
+ at(node) {
67623
+ if (this.node === void 0) {
67624
+ this.node = node;
67625
+ this.source = runtimeSource(node);
67626
+ }
67627
+ return this;
67628
+ }
67317
67629
  constructor(message, options) {
67318
67630
  super(message, options);
67319
67631
  this.name = "NSGetterRuntimeError";
67632
+ Object.defineProperties(this, {
67633
+ source: { writable: true, value: void 0 },
67634
+ node: { writable: true, value: void 0 },
67635
+ callerNode: { writable: true, value: void 0 },
67636
+ initializerValueId: { writable: true, value: void 0 },
67637
+ sourceOwnerId: { writable: true, value: void 0 }
67638
+ });
67320
67639
  }
67321
67640
  };
67322
67641
  UncompiledInitializerRuntimeError = class extends NSGetterRuntimeError {
@@ -67353,9 +67672,13 @@ function materializeInitializerDependencies(pending, materialize) {
67353
67672
  if (!(error instanceof PendingInitializerDependency)) throw error;
67354
67673
  if (!pending.has(error.valueId)) throw error;
67355
67674
  if (active.has(error.valueId)) {
67356
- throw new Error(
67357
- `Initializer dependency cycle: ${[...stack, error.valueId].join(" -> ")}.`
67675
+ const cycle = new NSGetterRuntimeError(
67676
+ `Initializer dependency cycle: ${[...stack, error.valueId].join(" -> ")}. Remove a dependency so these values can be initialized in order.`
67358
67677
  );
67678
+ cycle.initializerValueId = valueId;
67679
+ cycle.node = error.node;
67680
+ cycle.source = error.source;
67681
+ throw cycle;
67359
67682
  }
67360
67683
  active.add(error.valueId);
67361
67684
  stack.push(error.valueId);
@@ -73750,326 +74073,351 @@ function validateTryInstruction(instruction) {
73750
74073
  }
73751
74074
  function evalInstructions(instructions, scope, ctx, options) {
73752
74075
  for (const ins of instructions) {
73753
- consumeBudget(ctx, "workUnits", 1, "work unit");
73754
- switch (ins.type) {
73755
- case "variable" /* variable */: {
73756
- let value = evalPointer(ins.variable.pointer, scope, ctx);
73757
- if (ins.variable.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
73758
- value = coerceDecimalOperand(value, "variable initialization");
73759
- }
73760
- scope.setLocal(ins.variable.id, value);
73761
- break;
73762
- }
73763
- case "if" /* if */: {
73764
- let matched = false;
73765
- for (const branch of ins.branches) {
73766
- if (evalBooleanExpression(branch.expression, scope, ctx)) {
73767
- matched = true;
73768
- const r = evalInstructions(
73769
- branch.instructions,
73770
- scope,
73771
- ctx,
73772
- options
73773
- );
73774
- if (r.kind !== "fallthrough") return r;
73775
- break;
74076
+ try {
74077
+ consumeBudget(ctx, "workUnits", 1, "work unit");
74078
+ switch (ins.type) {
74079
+ case "variable" /* variable */: {
74080
+ let value = evalPointer(ins.variable.pointer, scope, ctx);
74081
+ if (ins.variable.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
74082
+ value = coerceDecimalOperand(value, "variable initialization");
73776
74083
  }
74084
+ scope.setLocal(ins.variable.id, value);
74085
+ break;
73777
74086
  }
73778
- if (!matched && ins.else) {
73779
- const r = evalInstructions(ins.else, scope, ctx, options);
73780
- if (r.kind !== "fallthrough") return r;
74087
+ case "if" /* if */: {
74088
+ let matched = false;
74089
+ for (const branch of ins.branches) {
74090
+ if (evalBooleanExpression(branch.expression, scope, ctx)) {
74091
+ matched = true;
74092
+ const r = evalInstructions(
74093
+ branch.instructions,
74094
+ scope,
74095
+ ctx,
74096
+ options
74097
+ );
74098
+ if (r.kind !== "fallthrough") return r;
74099
+ break;
74100
+ }
74101
+ }
74102
+ if (!matched && ins.else) {
74103
+ const r = evalInstructions(ins.else, scope, ctx, options);
74104
+ if (r.kind !== "fallthrough") return r;
74105
+ }
74106
+ break;
73781
74107
  }
73782
- break;
73783
- }
73784
- case "return" /* return */: {
73785
- const value = ins.pointer === null ? null : evalPointer(ins.pointer, scope, ctx);
73786
- return { kind: "return", value };
73787
- }
73788
- case "throw" /* throw */: {
73789
- const message = evalPointer(ins.pointer, scope, ctx);
73790
- throw new NSGetterRuntimeError(String(message));
73791
- }
73792
- case "assign" /* assign */: {
73793
- let value = evalPointer(ins.pointer, scope, ctx);
73794
- if (ins.target.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
73795
- value = coerceDecimalOperand(value, "assignment");
74108
+ case "return" /* return */: {
74109
+ const value = ins.pointer === null ? null : evalPointer(ins.pointer, scope, ctx);
74110
+ return { kind: "return", value };
73796
74111
  }
73797
- if (ins.target.pointer.type === "variable" /* variable */) {
73798
- assertVariableBindingWritable(scope, ins.target.pointer.variableId);
73799
- scope.setLocal(ins.target.pointer.variableId, value);
73800
- break;
74112
+ case "throw" /* throw */: {
74113
+ const message = evalPointer(ins.pointer, scope, ctx);
74114
+ throw new NSGetterRuntimeError(String(message));
73801
74115
  }
73802
- if (ins.target.writability === "setter" /* Setter */) {
73803
- dispatchNSSetter(
74116
+ case "assign" /* assign */: {
74117
+ let value = evalPointer(ins.pointer, scope, ctx);
74118
+ if (ins.target.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
74119
+ value = coerceDecimalOperand(value, "assignment");
74120
+ }
74121
+ if (ins.target.pointer.type === "variable" /* variable */) {
74122
+ assertVariableBindingWritable(scope, ins.target.pointer.variableId);
74123
+ scope.setLocal(ins.target.pointer.variableId, value);
74124
+ break;
74125
+ }
74126
+ if (ins.target.writability === "setter" /* Setter */) {
74127
+ dispatchNSSetter(
74128
+ ins.target.pointer,
74129
+ value,
74130
+ scope,
74131
+ ctx,
74132
+ options.writes
74133
+ );
74134
+ break;
74135
+ }
74136
+ const destination = validateAssignmentTarget(
73804
74137
  ins.target.pointer,
73805
- value,
73806
74138
  scope,
73807
- ctx,
73808
- options.writes
74139
+ ctx
73809
74140
  );
73810
- break;
73811
- }
73812
- const destination = validateAssignmentTarget(
73813
- ins.target.pointer,
73814
- scope,
73815
- ctx
73816
- );
73817
- const member = assignmentTargetMember(
73818
- ins.target.pointer,
73819
- scope,
73820
- ctx,
73821
- destination
73822
- );
73823
- const lookupAssignment = member !== null && isMemberLookupBase(member);
73824
- if (lookupAssignment) {
73825
- value = encodeLookupInitializerSelection(member, value, [], ctx);
73826
- }
73827
- let targetTypeInfo = ins.target.typeInfo;
73828
- if (lookupAssignment && targetTypeInfo.type !== 9 /* Lookup */ && targetTypeInfo.type !== 18 /* DialogueLookup */) {
73829
- targetTypeInfo = {
73830
- type: 9 /* Lookup */,
73831
- required: targetTypeInfo.required,
73832
- entryTypeInfo: targetTypeInfo,
73833
- collectionMemberId: member.collectionMemberId,
73834
- collectionValueId: member.collectionValueId
73835
- };
73836
- }
73837
- const assignedReferenceValueId = targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? findKnownRowIdByValueReference(value, ctx) : null;
73838
- applyOverlayAssignment(
73839
- ins.target.pointer,
73840
- targetTypeInfo,
73841
- value,
73842
- scope,
73843
- ctx,
73844
- destination
73845
- );
73846
- const unorderedReplacement = assignedReferenceValueId === null && member !== null && isMemberListBase(member) && listKindOf(member) === 1 /* Unordered */ && Array.isArray(value) ? unorderedReplacementProjection(
73847
- ins.target.pointer,
73848
- destination,
73849
- scope,
73850
- ctx
73851
- ) : null;
73852
- options.writes.push({
73853
- kind: "assign",
73854
- target: targetTypeInfo === ins.target.typeInfo ? ins.target : { ...ins.target, typeInfo: targetTypeInfo },
73855
- value: unorderedReplacement ?? value,
73856
- ...targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? {
73857
- valueId: value === null || value === void 0 ? void 0 : assignedReferenceValueId ?? void 0
73858
- } : {},
73859
- ...resolvedDestinationFieldsForAssign(
74141
+ const member = assignmentTargetMember(
73860
74142
  ins.target.pointer,
73861
74143
  scope,
73862
74144
  ctx,
73863
74145
  destination
73864
- )
73865
- });
73866
- break;
73867
- }
73868
- case "addActionListener" /* addActionListener */:
73869
- case "removeActionListener" /* removeActionListener */: {
73870
- evalActionListenerInstruction(ins, scope, ctx, options);
73871
- break;
73872
- }
73873
- case "addChangeListener" /* addChangeListener */:
73874
- case "removeChangeListener" /* removeChangeListener */: {
73875
- evalChangeListenerInstruction(ins, scope, ctx, options);
73876
- break;
73877
- }
73878
- case "collectionCall" /* collectionCall */: {
73879
- evalCollectionMutationInstruction(ins, scope, ctx, options);
73880
- break;
73881
- }
73882
- case "functionCall" /* functionCall */: {
73883
- evalPointer(ins.call, scope, ctx, "discarded");
73884
- break;
73885
- }
73886
- case "while" /* while */:
73887
- case "doWhile" /* doWhile */: {
73888
- let firstIteration = ins.type === "doWhile" /* doWhile */;
73889
- while (firstIteration || evalBooleanExpression(ins.condition, scope, ctx)) {
73890
- firstIteration = false;
73891
- consumeLoopIteration(ctx);
73892
- const result = evalInstructionsInChildScope(
73893
- ins.instructions,
74146
+ );
74147
+ const lookupAssignment = member !== null && isMemberLookupBase(member);
74148
+ if (lookupAssignment) {
74149
+ value = encodeLookupInitializerSelection(member, value, [], ctx);
74150
+ }
74151
+ let targetTypeInfo = ins.target.typeInfo;
74152
+ if (lookupAssignment && targetTypeInfo.type !== 9 /* Lookup */ && targetTypeInfo.type !== 18 /* DialogueLookup */) {
74153
+ targetTypeInfo = {
74154
+ type: 9 /* Lookup */,
74155
+ required: targetTypeInfo.required,
74156
+ entryTypeInfo: targetTypeInfo,
74157
+ collectionMemberId: member.collectionMemberId,
74158
+ collectionValueId: member.collectionValueId
74159
+ };
74160
+ }
74161
+ const assignedReferenceValueId = targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? findKnownRowIdByValueReference(value, ctx) : null;
74162
+ applyOverlayAssignment(
74163
+ ins.target.pointer,
74164
+ targetTypeInfo,
74165
+ value,
73894
74166
  scope,
73895
74167
  ctx,
73896
- options
73897
- );
73898
- if (result.kind === "return") return result;
73899
- if (result.kind === "break") break;
73900
- }
73901
- break;
73902
- }
73903
- case "for" /* for */: {
73904
- const parentBindingIds = [...scope.keys()];
73905
- const loopScope = createChildScope(scope);
73906
- try {
73907
- const initialValue = coerceAssignmentValue(
73908
- evalPointer(ins.initializer.pointer, loopScope, ctx),
73909
- ins.initializer.typeInfo,
73910
- "variable initialization"
74168
+ destination
73911
74169
  );
73912
- loopScope.setLocal(ins.initializer.id, initialValue);
73913
- while (evalBooleanExpression(ins.condition, loopScope, ctx)) {
73914
- consumeLoopIteration(ctx);
73915
- const result = evalInstructionsInChildScope(
73916
- ins.instructions,
73917
- loopScope,
74170
+ const unorderedReplacement = assignedReferenceValueId === null && member !== null && isMemberListBase(member) && listKindOf(member) === 1 /* Unordered */ && Array.isArray(value) ? unorderedReplacementProjection(
74171
+ ins.target.pointer,
74172
+ destination,
74173
+ scope,
74174
+ ctx
74175
+ ) : null;
74176
+ options.writes.push({
74177
+ kind: "assign",
74178
+ target: targetTypeInfo === ins.target.typeInfo ? ins.target : { ...ins.target, typeInfo: targetTypeInfo },
74179
+ value: unorderedReplacement ?? value,
74180
+ ...targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? {
74181
+ valueId: value === null || value === void 0 ? void 0 : assignedReferenceValueId ?? void 0
74182
+ } : {},
74183
+ ...resolvedDestinationFieldsForAssign(
74184
+ ins.target.pointer,
74185
+ scope,
73918
74186
  ctx,
73919
- options
73920
- );
73921
- if (result.kind === "return") return result;
73922
- if (result.kind === "break") break;
73923
- evalInstructions([ins.iterator], loopScope, ctx, options);
73924
- }
73925
- } finally {
73926
- synchronizeExistingBindings(scope, loopScope, parentBindingIds);
74187
+ destination
74188
+ )
74189
+ });
74190
+ break;
73927
74191
  }
73928
- break;
73929
- }
73930
- case "forEach" /* forEach */: {
73931
- const collection = evalPointer(ins.collectionPointer, scope, ctx);
73932
- const membership = snapshotCollectionMembership(collection, ctx);
73933
- const parentBindingIds = [...scope.keys()];
73934
- const loopScope = createChildScope(scope);
73935
- markReadonlyBinding(
73936
- loopScope,
73937
- ins.binding.id,
73938
- READONLY_FOREACH_BINDING_ERROR
73939
- );
73940
- try {
73941
- for (const rawEntry of membership) {
74192
+ case "addActionListener" /* addActionListener */:
74193
+ case "removeActionListener" /* removeActionListener */: {
74194
+ evalActionListenerInstruction(ins, scope, ctx, options);
74195
+ break;
74196
+ }
74197
+ case "addChangeListener" /* addChangeListener */:
74198
+ case "removeChangeListener" /* removeChangeListener */: {
74199
+ evalChangeListenerInstruction(ins, scope, ctx, options);
74200
+ break;
74201
+ }
74202
+ case "collectionCall" /* collectionCall */: {
74203
+ evalCollectionMutationInstruction(ins, scope, ctx, options);
74204
+ break;
74205
+ }
74206
+ case "functionCall" /* functionCall */: {
74207
+ evalPointer(ins.call, scope, ctx, "discarded");
74208
+ break;
74209
+ }
74210
+ case "while" /* while */:
74211
+ case "doWhile" /* doWhile */: {
74212
+ let firstIteration = ins.type === "doWhile" /* doWhile */;
74213
+ while (firstIteration || evalBooleanExpression(ins.condition, scope, ctx)) {
74214
+ firstIteration = false;
73942
74215
  consumeLoopIteration(ctx);
73943
- loopScope.setLocal(
73944
- ins.binding.id,
73945
- coerceAssignmentValue(
73946
- rawEntry.resolveAsReference ? resolveValueIfId(rawEntry.raw, ctx) : rawEntry.raw,
73947
- ins.binding.typeInfo,
73948
- "foreach binding"
73949
- )
73950
- );
73951
74216
  const result = evalInstructionsInChildScope(
73952
74217
  ins.instructions,
73953
- loopScope,
74218
+ scope,
73954
74219
  ctx,
73955
74220
  options
73956
74221
  );
73957
74222
  if (result.kind === "return") return result;
73958
74223
  if (result.kind === "break") break;
73959
74224
  }
73960
- } finally {
73961
- synchronizeExistingBindings(scope, loopScope, parentBindingIds);
73962
- }
73963
- break;
73964
- }
73965
- case "switch" /* switch */: {
73966
- validateSwitchInstruction(ins);
73967
- const selector = evalPointer(ins.selector, scope, ctx);
73968
- assertSwitchSelectorValue(selector, ins.selectorTypeInfo, ctx);
73969
- const matchedSection = ins.sections.find(
73970
- (section) => section.labels.some((label) => jsEqual(selector, label.value))
73971
- );
73972
- const selectedInstructions = matchedSection?.instructions ?? ins.defaultInstructions;
73973
- if (selectedInstructions === void 0 || selectedInstructions === null) {
73974
74225
  break;
73975
74226
  }
73976
- const parentBindingIds = [...scope.keys()];
73977
- const sectionScope = createChildScope(scope);
73978
- const result = (() => {
74227
+ case "for" /* for */: {
74228
+ const parentBindingIds = [...scope.keys()];
74229
+ const loopScope = createChildScope(scope);
73979
74230
  try {
73980
- return evalInstructions(
73981
- selectedInstructions,
73982
- sectionScope,
73983
- ctx,
73984
- options
74231
+ const initialValue = coerceAssignmentValue(
74232
+ evalPointer(ins.initializer.pointer, loopScope, ctx),
74233
+ ins.initializer.typeInfo,
74234
+ "variable initialization"
73985
74235
  );
74236
+ loopScope.setLocal(ins.initializer.id, initialValue);
74237
+ while (evalBooleanExpression(ins.condition, loopScope, ctx)) {
74238
+ consumeLoopIteration(ctx);
74239
+ const result = evalInstructionsInChildScope(
74240
+ ins.instructions,
74241
+ loopScope,
74242
+ ctx,
74243
+ options
74244
+ );
74245
+ if (result.kind === "return") return result;
74246
+ if (result.kind === "break") break;
74247
+ evalInstructions([ins.iterator], loopScope, ctx, options);
74248
+ }
73986
74249
  } finally {
73987
- synchronizeExistingBindings(scope, sectionScope, parentBindingIds);
74250
+ synchronizeExistingBindings(scope, loopScope, parentBindingIds);
73988
74251
  }
73989
- })();
73990
- if (result.kind === "break") break;
73991
- if (result.kind === "fallthrough") {
73992
- throw new CorruptNeoScriptIRError(
73993
- "Corrupt NeoScript switch IR: selected section reached its end."
73994
- );
74252
+ break;
73995
74253
  }
73996
- return result;
73997
- }
73998
- case "try" /* try */: {
73999
- validateTryInstruction(ins);
74000
- const parentBindingIds = [...scope.keys()];
74001
- const tryScope = createChildScope(scope);
74002
- let tryResult;
74003
- try {
74254
+ case "forEach" /* forEach */: {
74255
+ const collection = evalPointer(ins.collectionPointer, scope, ctx);
74256
+ const membership = snapshotCollectionMembership(collection, ctx);
74257
+ const parentBindingIds = [...scope.keys()];
74258
+ const loopScope = createChildScope(scope);
74259
+ markReadonlyBinding(
74260
+ loopScope,
74261
+ ins.binding.id,
74262
+ READONLY_FOREACH_BINDING_ERROR
74263
+ );
74004
74264
  try {
74005
- tryResult = evalInstructions(
74006
- ins.instructions,
74007
- tryScope,
74008
- ctx,
74009
- options
74010
- );
74265
+ for (const rawEntry of membership) {
74266
+ consumeLoopIteration(ctx);
74267
+ loopScope.setLocal(
74268
+ ins.binding.id,
74269
+ coerceAssignmentValue(
74270
+ rawEntry.resolveAsReference ? resolveValueIfId(rawEntry.raw, ctx) : rawEntry.raw,
74271
+ ins.binding.typeInfo,
74272
+ "foreach binding"
74273
+ )
74274
+ );
74275
+ const result = evalInstructionsInChildScope(
74276
+ ins.instructions,
74277
+ loopScope,
74278
+ ctx,
74279
+ options
74280
+ );
74281
+ if (result.kind === "return") return result;
74282
+ if (result.kind === "break") break;
74283
+ }
74011
74284
  } finally {
74012
- synchronizeExistingBindings(scope, tryScope, parentBindingIds);
74285
+ synchronizeExistingBindings(scope, loopScope, parentBindingIds);
74013
74286
  }
74014
- } catch (error) {
74015
- if (!isCatchableNeoScriptRuntimeError(error)) throw error;
74016
- let matched = false;
74017
- for (const clause of ins.catches) {
74018
- const catchScope = createChildScope(scope);
74019
- catchScope.setLocal(clause.binding.id, error.message);
74020
- markReadonlyBinding(
74021
- catchScope,
74022
- clause.binding.id,
74023
- READONLY_CATCH_BINDING_ERROR
74024
- );
74025
- const filter = clause.filter;
74026
- let filterMatches = filter === null || filter === void 0;
74027
- if (filter !== null && filter !== void 0) {
74028
- try {
74029
- filterMatches = evalBooleanExpression(filter, catchScope, ctx);
74030
- } catch (filterError) {
74031
- if (!isCatchableNeoScriptRuntimeError(filterError)) {
74032
- synchronizeExistingBindings(
74033
- scope,
74034
- catchScope,
74035
- parentBindingIds
74036
- );
74037
- throw filterError;
74038
- }
74039
- filterMatches = false;
74040
- }
74041
- }
74042
- if (!filterMatches) {
74043
- synchronizeExistingBindings(scope, catchScope, parentBindingIds);
74044
- continue;
74287
+ break;
74288
+ }
74289
+ case "switch" /* switch */: {
74290
+ validateSwitchInstruction(ins);
74291
+ const selector = evalPointer(ins.selector, scope, ctx);
74292
+ assertSwitchSelectorValue(selector, ins.selectorTypeInfo, ctx);
74293
+ const matchedSection = ins.sections.find(
74294
+ (section) => section.labels.some((label) => jsEqual(selector, label.value))
74295
+ );
74296
+ const selectedInstructions = matchedSection?.instructions ?? ins.defaultInstructions;
74297
+ if (selectedInstructions === void 0 || selectedInstructions === null) {
74298
+ break;
74299
+ }
74300
+ const parentBindingIds = [...scope.keys()];
74301
+ const sectionScope = createChildScope(scope);
74302
+ const result = (() => {
74303
+ try {
74304
+ return evalInstructions(
74305
+ selectedInstructions,
74306
+ sectionScope,
74307
+ ctx,
74308
+ options
74309
+ );
74310
+ } finally {
74311
+ synchronizeExistingBindings(
74312
+ scope,
74313
+ sectionScope,
74314
+ parentBindingIds
74315
+ );
74045
74316
  }
74046
- matched = true;
74047
- let catchResult;
74317
+ })();
74318
+ if (result.kind === "break") break;
74319
+ if (result.kind === "fallthrough") {
74320
+ throw new CorruptNeoScriptIRError(
74321
+ "Corrupt NeoScript switch IR: selected section reached its end."
74322
+ );
74323
+ }
74324
+ return result;
74325
+ }
74326
+ case "try" /* try */: {
74327
+ validateTryInstruction(ins);
74328
+ const parentBindingIds = [...scope.keys()];
74329
+ const tryScope = createChildScope(scope);
74330
+ let tryResult;
74331
+ try {
74048
74332
  try {
74049
- catchResult = evalInstructions(
74050
- clause.instructions,
74051
- catchScope,
74333
+ tryResult = evalInstructions(
74334
+ ins.instructions,
74335
+ tryScope,
74052
74336
  ctx,
74053
74337
  options
74054
74338
  );
74055
74339
  } finally {
74056
- synchronizeExistingBindings(scope, catchScope, parentBindingIds);
74340
+ synchronizeExistingBindings(scope, tryScope, parentBindingIds);
74057
74341
  }
74058
- if (catchResult.kind !== "fallthrough") return catchResult;
74342
+ } catch (error) {
74343
+ if (!isCatchableNeoScriptRuntimeError(error)) throw error;
74344
+ let matched = false;
74345
+ for (const clause of ins.catches) {
74346
+ const catchScope = createChildScope(scope);
74347
+ catchScope.setLocal(clause.binding.id, error.message);
74348
+ markReadonlyBinding(
74349
+ catchScope,
74350
+ clause.binding.id,
74351
+ READONLY_CATCH_BINDING_ERROR
74352
+ );
74353
+ const filter = clause.filter;
74354
+ let filterMatches = filter === null || filter === void 0;
74355
+ if (filter !== null && filter !== void 0) {
74356
+ try {
74357
+ filterMatches = evalBooleanExpression(
74358
+ filter,
74359
+ catchScope,
74360
+ ctx
74361
+ );
74362
+ } catch (filterError) {
74363
+ if (!isCatchableNeoScriptRuntimeError(filterError)) {
74364
+ synchronizeExistingBindings(
74365
+ scope,
74366
+ catchScope,
74367
+ parentBindingIds
74368
+ );
74369
+ throw filterError;
74370
+ }
74371
+ filterMatches = false;
74372
+ }
74373
+ }
74374
+ if (!filterMatches) {
74375
+ synchronizeExistingBindings(
74376
+ scope,
74377
+ catchScope,
74378
+ parentBindingIds
74379
+ );
74380
+ continue;
74381
+ }
74382
+ matched = true;
74383
+ let catchResult;
74384
+ try {
74385
+ catchResult = evalInstructions(
74386
+ clause.instructions,
74387
+ catchScope,
74388
+ ctx,
74389
+ options
74390
+ );
74391
+ } finally {
74392
+ synchronizeExistingBindings(
74393
+ scope,
74394
+ catchScope,
74395
+ parentBindingIds
74396
+ );
74397
+ }
74398
+ if (catchResult.kind !== "fallthrough") return catchResult;
74399
+ break;
74400
+ }
74401
+ if (!matched) throw error;
74059
74402
  break;
74060
74403
  }
74061
- if (!matched) throw error;
74404
+ if (tryResult.kind !== "fallthrough") return tryResult;
74062
74405
  break;
74063
74406
  }
74064
- if (tryResult.kind !== "fallthrough") return tryResult;
74065
- break;
74407
+ case "break" /* break */:
74408
+ return { kind: "break" };
74409
+ case "continue" /* continue */:
74410
+ return { kind: "continue" };
74411
+ }
74412
+ void touchUnknown(ins);
74413
+ } catch (error) {
74414
+ if (error instanceof NSGetterRuntimeError) {
74415
+ error.at(ins);
74416
+ if (ins.type === "assign" /* assign */ && ins.target.writability === "setter" /* Setter */)
74417
+ error.callerNode = ins;
74066
74418
  }
74067
- case "break" /* break */:
74068
- return { kind: "break" };
74069
- case "continue" /* continue */:
74070
- return { kind: "continue" };
74419
+ throw error;
74071
74420
  }
74072
- void touchUnknown(ins);
74073
74421
  }
74074
74422
  return { kind: "fallthrough" };
74075
74423
  }
@@ -74538,7 +74886,7 @@ function validateAssignmentTarget(target, scope, ctx, destination) {
74538
74886
  );
74539
74887
  }
74540
74888
  if (index < 0 || index >= receiver.length) {
74541
- throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
74889
+ throw new NSGetterRuntimeError(listIndexError(index, receiver.length));
74542
74890
  }
74543
74891
  }
74544
74892
  return resolved;
@@ -74644,15 +74992,21 @@ function dispatchNSSetter(target, value, scope, ctx, writes) {
74644
74992
  }
74645
74993
  const innerStack = new Set(stack);
74646
74994
  innerStack.add(memberId);
74647
- evaluateNSSetter(
74648
- setter,
74649
- {
74650
- ...bindThisValue(ctx, receiver),
74651
- __setterCallStack: innerStack
74652
- },
74653
- value,
74654
- writes
74655
- );
74995
+ try {
74996
+ evaluateNSSetter(
74997
+ setter,
74998
+ {
74999
+ ...bindThisValue(ctx, receiver),
75000
+ __setterCallStack: innerStack
75001
+ },
75002
+ value,
75003
+ writes
75004
+ );
75005
+ } catch (error) {
75006
+ if (error instanceof NSGetterRuntimeError)
75007
+ error.sourceOwnerId ??= compiledBodyOwnerId(memberId, setter, ctx);
75008
+ throw error;
75009
+ }
74656
75010
  }
74657
75011
  function resolveEffectiveNSPropertyMemberId(staticMemberId, receiver, ctx) {
74658
75012
  const schemaKey = cachedSchemaKeyForMember(staticMemberId, ctx);
@@ -74966,7 +75320,7 @@ function applyLocalCollectionMutation(targetValue, mutation, args, ctx, unique =
74966
75320
  const list = listArg(targetValue, "RemoveAt target");
74967
75321
  const index = numberArg(args[0], "RemoveAt index");
74968
75322
  if (index < 0 || index >= list.length) {
74969
- throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
75323
+ throw new NSGetterRuntimeError(listIndexError(index, list.length));
74970
75324
  }
74971
75325
  list.splice(index, 1);
74972
75326
  return index;
@@ -75036,7 +75390,7 @@ function validateCollectionMutation(targetValue, mutation, args) {
75036
75390
  );
75037
75391
  if (index < 0 || index > list.length) {
75038
75392
  throw new NSGetterRuntimeError(
75039
- `List insertion index out of bounds: ${index}`
75393
+ `List insertion index out of bounds: ${index}; use an index from 0 to ${list.length}.`
75040
75394
  );
75041
75395
  }
75042
75396
  return;
@@ -75045,7 +75399,7 @@ function validateCollectionMutation(targetValue, mutation, args) {
75045
75399
  const list = listArg(targetValue, "RemoveAt target");
75046
75400
  const index = numberArg(args[0], "RemoveAt index");
75047
75401
  if (index < 0 || index >= list.length) {
75048
- throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
75402
+ throw new NSGetterRuntimeError(listIndexError(index, list.length));
75049
75403
  }
75050
75404
  return;
75051
75405
  }
@@ -75069,6 +75423,11 @@ function validateCollectionMutation(targetValue, mutation, args) {
75069
75423
  }
75070
75424
  listArg(targetValue, `${mutation} target`);
75071
75425
  }
75426
+ function listIndexError(index, length) {
75427
+ if (length === 0)
75428
+ return `List index out of bounds: ${String(index)}; the list is empty. Add an entry before indexing it.`;
75429
+ return `List index out of bounds: ${String(index)}; list length is ${length}. Use an index from 0 to ${length - 1}.`;
75430
+ }
75072
75431
  function listArg(value, name) {
75073
75432
  if (Array.isArray(value)) return value;
75074
75433
  throw new NSGetterRuntimeError(`${name} must be a list`);
@@ -75165,6 +75524,17 @@ function evalStaticMember(memberId, ctx) {
75165
75524
  return shouldUnwrapSingleLookupValue(member) ? unwrapSingleLookupValue(readRowValue(row, ctx), ctx) : resolveLocalizedRowValueForMember(row, member, ctx);
75166
75525
  }
75167
75526
  function evalPointer(pointer, scope, ctx, resultUsage = "value") {
75527
+ try {
75528
+ return evalPointerCore(pointer, scope, ctx, resultUsage);
75529
+ } catch (error) {
75530
+ if (error instanceof NSGetterRuntimeError) {
75531
+ error.at(pointer);
75532
+ error.callerNode = pointer;
75533
+ }
75534
+ throw error;
75535
+ }
75536
+ }
75537
+ function evalPointerCore(pointer, scope, ctx, resultUsage = "value") {
75168
75538
  switch (pointer.type) {
75169
75539
  case "value" /* value */: {
75170
75540
  const value = pointer.value.value;
@@ -75262,7 +75632,7 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
75262
75632
  const v = evalPointer(pointer.pointer, scope, ctx);
75263
75633
  if (v === null || v === void 0) {
75264
75634
  throw new NSGetterRuntimeError(
75265
- "Unexpectedly found null while force-unwrapping a value"
75635
+ "Cannot unwrap null with '!'. Check for null first or provide a fallback with '??'."
75266
75636
  );
75267
75637
  }
75268
75638
  return v;
@@ -75416,6 +75786,10 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
75416
75786
  runtimeSignature.returnTypeInfo.type === NS_TYPE_VOID,
75417
75787
  runtimeSignature
75418
75788
  );
75789
+ } catch (error) {
75790
+ if (error instanceof NSGetterRuntimeError)
75791
+ error.sourceOwnerId ??= compiledBodyOwnerId(member.id, action, ctx);
75792
+ throw error;
75419
75793
  } finally {
75420
75794
  state.functionStack.pop();
75421
75795
  }
@@ -75701,13 +76075,19 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
75701
76075
  );
75702
76076
  }
75703
76077
  const runtimeSignature = receiver === null ? signature : substituteCallableSignatureForReceiver(signature, receiver, ctx);
75704
- return executeCompiledFunction(
75705
- action,
75706
- bindThisValue(ctx, receiver),
75707
- args,
75708
- runtimeSignature.returnTypeInfo.type === NS_TYPE_VOID,
75709
- runtimeSignature
75710
- );
76078
+ try {
76079
+ return executeCompiledFunction(
76080
+ action,
76081
+ bindThisValue(ctx, receiver),
76082
+ args,
76083
+ runtimeSignature.returnTypeInfo.type === NS_TYPE_VOID,
76084
+ runtimeSignature
76085
+ );
76086
+ } catch (error) {
76087
+ if (error instanceof NSGetterRuntimeError)
76088
+ error.sourceOwnerId ??= compiledBodyOwnerId(member.id, action, ctx);
76089
+ throw error;
76090
+ }
75711
76091
  }
75712
76092
  if (member.kind === 25 /* NSDelegate */) {
75713
76093
  let nested;
@@ -76285,6 +76665,14 @@ function runtimeTypeInfoForMember(rawMember, env, ctx, seen) {
76285
76665
  required: member.kind === 0 /* Null */ ? false : isRequiredMember2(member)
76286
76666
  };
76287
76667
  }
76668
+ function compiledBodyOwnerId(memberId, body, ctx) {
76669
+ return walkExtendsMemberChain(
76670
+ memberId,
76671
+ ctx.vm.members,
76672
+ (member) => getField(member, "action") === body || getField(member, "getter") === body || getField(member, "setter") === body ? member.id : void 0,
76673
+ { memberById: (id2) => evalMemberById(ctx.vm, id2) }
76674
+ ) ?? memberId;
76675
+ }
76288
76676
  function resolveCompiledNSFunction(memberId, ctx) {
76289
76677
  const cache = evaluatorResolutionCache(ctx).compiledFunctionByMemberId;
76290
76678
  if (cache.has(memberId)) return cache.get(memberId) ?? null;
@@ -76421,15 +76809,21 @@ function dispatchNSGetterById(memberId, receiver, ctx) {
76421
76809
  }
76422
76810
  const innerStack = new Set(stack);
76423
76811
  innerStack.add(memberId);
76424
- return evaluateNSGetterWithEffects(
76425
- getter,
76426
- {
76427
- ...bindThisValue(ctx, receiver),
76428
- __getterCallStack: innerStack
76429
- },
76430
- [],
76431
- { sessionValueExport: "defer" }
76432
- ).value;
76812
+ try {
76813
+ return evaluateNSGetterWithEffects(
76814
+ getter,
76815
+ {
76816
+ ...bindThisValue(ctx, receiver),
76817
+ __getterCallStack: innerStack
76818
+ },
76819
+ [],
76820
+ { sessionValueExport: "defer" }
76821
+ ).value;
76822
+ } catch (error) {
76823
+ if (error instanceof NSGetterRuntimeError)
76824
+ error.sourceOwnerId ??= compiledBodyOwnerId(memberId, getter, ctx);
76825
+ throw error;
76826
+ }
76433
76827
  }
76434
76828
  function resolveCompiledGetter(memberId, ctx) {
76435
76829
  const cache = evaluatorResolutionCache(ctx).compiledGetterByMemberId;
@@ -76648,10 +77042,12 @@ function evalKeyOf(keyOf, scope, ctx, optional2 = false, memberId, onDestination
76648
77042
  );
76649
77043
  }
76650
77044
  const idx = typeof key === "number" ? key : NaN;
76651
- if (!Number.isInteger(idx) || idx < 0 || idx >= receiver.length) {
77045
+ if (!Number.isInteger(idx))
76652
77046
  throw new NSGetterRuntimeError(
76653
- `List index out of bounds: ${String(key)}`
77047
+ `List index must be an integer; received ${String(key)}.`
76654
77048
  );
77049
+ if (idx < 0 || idx >= receiver.length) {
77050
+ throw new NSGetterRuntimeError(listIndexError(key, receiver.length));
76655
77051
  }
76656
77052
  return resolveCollectionEntry(receiver, receiver[idx], idx, ctx);
76657
77053
  }
@@ -76682,7 +77078,9 @@ function evalKeyOf(keyOf, scope, ctx, optional2 = false, memberId, onDestination
76682
77078
  if (expanded !== null && Object.prototype.hasOwnProperty.call(expanded, k)) {
76683
77079
  return resolveValueIfId(expanded[k], ctx);
76684
77080
  }
76685
- throw new NSGetterRuntimeError(`Missing key '${k}' on object`);
77081
+ throw new NSGetterRuntimeError(
77082
+ `Missing key '${k}' on object. Check that the key exists before reading it.`
77083
+ );
76686
77084
  }
76687
77085
  throw new NSGetterRuntimeError(
76688
77086
  `Cannot index into ${typeof receiver} with key '${String(key)}'`
@@ -78535,7 +78933,7 @@ function evalFunction(fn, scope, ctx) {
78535
78933
  if (found !== sentinel) return found;
78536
78934
  if (fn.type === "first" /* first */) {
78537
78935
  throw new NSGetterRuntimeError(
78538
- innerFn ? "First() found no matching entry" : "First() called on an empty collection"
78936
+ innerFn ? "First() found no matching entry. Use FirstOrDefault() and handle null if no match is expected." : "First() called on an empty collection. Check that it has entries, or use FirstOrDefault() and handle null."
78539
78937
  );
78540
78938
  }
78541
78939
  return null;
@@ -79596,12 +79994,18 @@ function runDeclaredConstructorChain(args) {
79596
79994
  });
79597
79995
  }
79598
79996
  args.applyBaseInitializerBlock(record6, args.argumentValues, args.thisValue);
79599
- executeCompiledFunction(
79600
- record6.action,
79601
- bindThisValue(ctx, args.thisValue),
79602
- args.argumentValues,
79603
- true
79604
- );
79997
+ try {
79998
+ executeCompiledFunction(
79999
+ record6.action,
80000
+ bindThisValue(ctx, args.thisValue),
80001
+ args.argumentValues,
80002
+ true
80003
+ );
80004
+ } catch (error) {
80005
+ if (error instanceof NSGetterRuntimeError)
80006
+ error.sourceOwnerId ??= record6.id;
80007
+ throw error;
80008
+ }
79605
80009
  }
79606
80010
  function evaluateBaseInitializerFields(args) {
79607
80011
  const { record: record6, ctx } = args;
@@ -83055,15 +83459,27 @@ function evaluateMemberInitializer(args) {
83055
83459
  __constructedFieldSchemaKeysByValue: /* @__PURE__ */ new WeakMap(),
83056
83460
  __createdStorageKeyDeclarations: args.storageKeyDeclarations ?? /* @__PURE__ */ new Map()
83057
83461
  };
83058
- const result = evaluateNSGetterWithEffects(
83059
- compiled,
83060
- ctx,
83061
- args.argumentValues ?? [],
83062
- {
83063
- exportInitializerValues: true,
83064
- initializerResult: { member: args.member, genericEnv: args.genericEnv }
83462
+ const result = (() => {
83463
+ try {
83464
+ return evaluateNSGetterWithEffects(
83465
+ compiled,
83466
+ ctx,
83467
+ args.argumentValues ?? [],
83468
+ {
83469
+ exportInitializerValues: true,
83470
+ initializerResult: {
83471
+ member: args.member,
83472
+ genericEnv: args.genericEnv
83473
+ }
83474
+ }
83475
+ );
83476
+ } catch (error) {
83477
+ if (error instanceof NSGetterRuntimeError) {
83478
+ error.initializerValueId ??= args.sourceValueId ?? void 0;
83479
+ }
83480
+ throw error;
83065
83481
  }
83066
- );
83482
+ })();
83067
83483
  const created = result.createdSessionValues ?? [];
83068
83484
  if (isMemberLookupBase(args.member)) {
83069
83485
  return encodeLookupInitializerResult(
@@ -83106,6 +83522,7 @@ var evaluatorLookupsByDocument, headlessResolversByLookupKey, indexedEvaluatorLo
83106
83522
  var init_evaluateInitializer = __esm({
83107
83523
  "../src/runtime/neoscript/evaluateInitializer.ts"() {
83108
83524
  "use strict";
83525
+ init_NSGetterRuntimeError();
83109
83526
  init_declaration_default_source();
83110
83527
  init_member_change_listeners();
83111
83528
  init_initializer_dependencies();
@@ -87352,7 +87769,7 @@ function compileStrict(code, context) {
87352
87769
  if (cached !== void 0) {
87353
87770
  neoScriptBodyCompileCache.delete(cacheKey);
87354
87771
  neoScriptBodyCompileCache.set(cacheKey, cached);
87355
- return structuredClone(cached);
87772
+ return cloneWithRuntimeSources(cached);
87356
87773
  }
87357
87774
  const compiled = assertNeoScriptCompiles(
87358
87775
  {
@@ -87369,7 +87786,7 @@ function compileStrict(code, context) {
87369
87786
  "A standalone NeoScript compiler entry point returned property units."
87370
87787
  );
87371
87788
  }
87372
- neoScriptBodyCompileCache.set(cacheKey, structuredClone(compiled));
87789
+ neoScriptBodyCompileCache.set(cacheKey, cloneWithRuntimeSources(compiled));
87373
87790
  while (neoScriptBodyCompileCache.size > NEOSCRIPT_BODY_COMPILE_CACHE_LIMIT) {
87374
87791
  const oldest = neoScriptBodyCompileCache.keys().next().value;
87375
87792
  if (typeof oldest !== "string") break;
@@ -87422,6 +87839,7 @@ var NEOSCRIPT_BODY_COMPILE_CACHE_LIMIT, neoScriptBodyCompileCache, neoScriptProj
87422
87839
  var init_compile = __esm({
87423
87840
  "../src/database/neoscript/compile.ts"() {
87424
87841
  "use strict";
87842
+ init_runtime_source();
87425
87843
  init_compiler();
87426
87844
  init_strict_compile_error();
87427
87845
  init_project();
@@ -90561,9 +90979,9 @@ var init_project_record_migrations = __esm({
90561
90979
  });
90562
90980
 
90563
90981
  // ../src/database/member-default-reference-ids.ts
90564
- function collectCandidateValueIds(sources) {
90982
+ function collectCandidateValueIds(sources2) {
90565
90983
  const candidates = [];
90566
- for (const source of sources) {
90984
+ for (const source of sources2) {
90567
90985
  if (typeof source !== "object" || source === null || !("value" in source))
90568
90986
  continue;
90569
90987
  const value = source.value;
@@ -112447,6 +112865,22 @@ function createGenerationContext(exportData, diagnostics, docs = EMPTY_UNITY_COD
112447
112865
  `${exportData.project.name}Neo`,
112448
112866
  "NeoProject"
112449
112867
  );
112868
+ const enums = Object.values(exportData.enums);
112869
+ const enumNames = buildEnumNameMap(enums, diagnostics);
112870
+ const typeNames = buildNameMap(
112871
+ Object.values(exportData.classes),
112872
+ "Type",
112873
+ diagnostics
112874
+ );
112875
+ const interfaceNames = buildNameMap(
112876
+ Object.values(exportData.interfaces),
112877
+ "Interface",
112878
+ diagnostics
112879
+ );
112880
+ const dialogueGroupClassNames = buildDialogueGroupClassNameMap(
112881
+ dialogueGroups,
112882
+ diagnostics
112883
+ );
112450
112884
  return {
112451
112885
  exportData,
112452
112886
  docs,
@@ -112454,22 +112888,23 @@ function createGenerationContext(exportData, diagnostics, docs = EMPTY_UNITY_COD
112454
112888
  classes: Object.values(exportData.classes),
112455
112889
  interfaces: Object.values(exportData.interfaces),
112456
112890
  diagnostics,
112457
- enumNames: buildEnumNameMap(Object.values(exportData.enums), diagnostics),
112458
- enumOptionFieldNames: /* @__PURE__ */ new Map(),
112459
- typeNames: buildNameMap(
112460
- Object.values(exportData.classes),
112461
- "Type",
112462
- diagnostics
112463
- ),
112464
- interfaceNames: buildNameMap(
112465
- Object.values(exportData.interfaces),
112466
- "Interface",
112467
- diagnostics
112468
- ),
112469
- dialogueGroupClassNames: buildDialogueGroupClassNameMap(
112470
- dialogueGroups,
112891
+ enumNames,
112892
+ enumOptionsNames: buildEnumOptionsNameMap(
112893
+ enums,
112894
+ enumNames,
112895
+ [
112896
+ projectClassName,
112897
+ ...enumNames.values(),
112898
+ ...typeNames.values(),
112899
+ ...interfaceNames.values(),
112900
+ ...dialogueGroupClassNames.values()
112901
+ ],
112471
112902
  diagnostics
112472
112903
  ),
112904
+ enumOptionFieldNames: /* @__PURE__ */ new Map(),
112905
+ typeNames,
112906
+ interfaceNames,
112907
+ dialogueGroupClassNames,
112473
112908
  dialogueGroupPropertyNames: buildDialogueGroupPropertyNameMap(
112474
112909
  dialogueGroups,
112475
112910
  diagnostics
@@ -112550,6 +112985,29 @@ function buildEnumNameMap(enums, diagnostics) {
112550
112985
  }
112551
112986
  return names;
112552
112987
  }
112988
+ function buildEnumOptionsNameMap(enums, enumNames, typeNames, diagnostics) {
112989
+ const taken = new Set(typeNames);
112990
+ const names = /* @__PURE__ */ new Map();
112991
+ for (const enumDef of enums) {
112992
+ const enumName = enumNames.get(enumDef.id);
112993
+ if (enumName === void 0) continue;
112994
+ const baseName = `${enumName}Options`;
112995
+ if (sdkRuntimeEnumBinding(enumDef.id) !== void 0 || !taken.has(baseName)) {
112996
+ taken.add(baseName);
112997
+ names.set(enumDef.id, baseName);
112998
+ continue;
112999
+ }
113000
+ const uniqueName = `${baseName}_${shortId(enumDef.id)}`;
113001
+ diagnostics.push({
113002
+ severity: "warning",
113003
+ message: `Generated C# options class "${baseName}" for enum "${enumDef.name}" collides with another generated type; using "${uniqueName}".`,
113004
+ path: `enums.${enumDef.id}.name`
113005
+ });
113006
+ taken.add(uniqueName);
113007
+ names.set(enumDef.id, uniqueName);
113008
+ }
113009
+ return names;
113010
+ }
112553
113011
  function buildDialogueGroupClassNameMap(groups, diagnostics) {
112554
113012
  const baseNames = buildNameMap(groups, "DialogueGroup", diagnostics);
112555
113013
  const used = /* @__PURE__ */ new Map();
@@ -116860,9 +117318,9 @@ function liveReferenceSourcesByTarget(records2) {
116860
117318
  record6.recordKind,
116861
117319
  record6.data
116862
117320
  )) {
116863
- const sources = sourcesByTarget.get(target);
116864
- if (sources === void 0) sourcesByTarget.set(target, [key]);
116865
- else sources.push(key);
117321
+ const sources2 = sourcesByTarget.get(target);
117322
+ if (sources2 === void 0) sourcesByTarget.set(target, [key]);
117323
+ else sources2.push(key);
116866
117324
  }
116867
117325
  }
116868
117326
  return sourcesByTarget;
@@ -117255,6 +117713,116 @@ var init_schema_source_validation = __esm({
117255
117713
  }
117256
117714
  });
117257
117715
 
117716
+ // src/project-source/runtime-diagnostic.ts
117717
+ function initializerRuntimeDiagnostic(error, sites, texts, analysis) {
117718
+ if (error instanceof SchemaSourceError) return error;
117719
+ const valueId = error instanceof NSGetterRuntimeError ? error.initializerValueId : error instanceof StoredClosureCaptureError ? error.valueId : void 0;
117720
+ const site = valueId === void 0 ? void 0 : sites.get(valueId);
117721
+ let position = site === void 0 ? void 0 : composeReferenceSitePosition(site, texts);
117722
+ if (error instanceof NSGetterRuntimeError && error.source !== void 0) {
117723
+ const source = error.source;
117724
+ if (source.uri !== void 0 && texts.get(source.uri) === source.text) {
117725
+ position = { file: source.uri, line: source.line, column: source.column };
117726
+ } else {
117727
+ const bodyLex = lex(source.text);
117728
+ let tokens = bodyLex.tokens.filter(
117729
+ (token) => token.kind !== "comment" && token.kind !== "eof"
117730
+ );
117731
+ const targetOffset = bodyLex.source.offsetAt({
117732
+ line: source.line - 1,
117733
+ character: source.column - 1
117734
+ });
117735
+ if (tokens[0]?.text === "return" && tokens.at(-1)?.text === ";")
117736
+ tokens = tokens.slice(1, -1);
117737
+ const target = tokens.findIndex((token) => token.end > targetOffset);
117738
+ const matches = [];
117739
+ if (tokens.length > 0 && target >= 0) {
117740
+ const prefix = new Array(tokens.length).fill(0);
117741
+ for (let i2 = 1, j = 0; i2 < tokens.length; i2++) {
117742
+ while (j > 0 && tokens[i2].text !== tokens[j].text)
117743
+ j = prefix[j - 1];
117744
+ if (tokens[i2].text === tokens[j].text) j++;
117745
+ prefix[i2] = j;
117746
+ }
117747
+ for (const [file, text] of texts) {
117748
+ const fileLex = lex(text);
117749
+ const candidates = fileLex.tokens.filter(
117750
+ (token) => token.kind !== "comment" && token.kind !== "eof"
117751
+ );
117752
+ for (let i2 = 0, j = 0; i2 < candidates.length; i2++) {
117753
+ while (j > 0 && candidates[i2].text !== tokens[j].text)
117754
+ j = prefix[j - 1];
117755
+ if (candidates[i2].text === tokens[j].text) j++;
117756
+ if (j !== tokens.length) continue;
117757
+ const matched = candidates[i2 - j + 1 + target];
117758
+ const point = fileLex.source.positionAt(
117759
+ matched.start + Math.max(0, targetOffset - tokens[target].start)
117760
+ );
117761
+ matches.push({
117762
+ file,
117763
+ line: point.line + 1,
117764
+ column: point.character + 1
117765
+ });
117766
+ j = prefix[j - 1];
117767
+ }
117768
+ }
117769
+ }
117770
+ const owner = sourceOwnerLocation(analysis, error.sourceOwnerId);
117771
+ const scoped = owner === void 0 ? matches : matches.filter((match) => {
117772
+ if (match.file !== owner.uri) return false;
117773
+ const line = match.line - 1;
117774
+ const column = match.column - 1;
117775
+ return (line > owner.range.start.line || line === owner.range.start.line && column >= owner.range.start.character) && (line < owner.range.end.line || line === owner.range.end.line && column < owner.range.end.character);
117776
+ });
117777
+ if (scoped.length === 1) position = scoped[0];
117778
+ else if (matches.length === 1) position = matches[0];
117779
+ }
117780
+ }
117781
+ let message = String(error);
117782
+ if (error instanceof NSGetterRuntimeError) message = error.diagnosticMessage;
117783
+ else if (error instanceof Error) message = error.message;
117784
+ return new SchemaSourceError(
117785
+ position === void 0 && valueId !== void 0 ? `Initializer for value "${valueId}": ${message}` : message,
117786
+ position?.file ?? "<initializer-materialization>",
117787
+ position?.line ?? 1,
117788
+ position?.column ?? 1
117789
+ );
117790
+ }
117791
+ function sourceOwnerLocation(analysis, id2) {
117792
+ if (analysis === null || analysis === void 0 || id2 === void 0)
117793
+ return void 0;
117794
+ for (const declaration of analysis.schema.classes) {
117795
+ for (const member of declaration.members) {
117796
+ if (materializedId(
117797
+ member,
117798
+ "member",
117799
+ `${declaration.name}.${member.name}`
117800
+ ) === id2)
117801
+ return member.source;
117802
+ }
117803
+ for (const constructor2 of declaration.constructors) {
117804
+ if (declaredConstructorId2(declaration, constructor2) === id2)
117805
+ return constructor2.source;
117806
+ }
117807
+ }
117808
+ const body = analysis.compiledBodies.find(
117809
+ (candidate) => candidate.memberId === id2
117810
+ );
117811
+ return body === void 0 ? void 0 : { uri: body.uri, range: body.range };
117812
+ }
117813
+ var init_runtime_diagnostic = __esm({
117814
+ "src/project-source/runtime-diagnostic.ts"() {
117815
+ "use strict";
117816
+ init_lower_support();
117817
+ init_lower_constructors();
117818
+ init_NSGetterRuntimeError();
117819
+ init_lexer();
117820
+ init_source_diagnostics();
117821
+ init_value_sources();
117822
+ init_stored_closure_captures();
117823
+ }
117824
+ });
117825
+
117258
117826
  // src/project-source/listener-lowering.ts
117259
117827
  function lowerListenerBindings(args) {
117260
117828
  const failures = [];
@@ -123668,21 +124236,40 @@ function materializedInitializerReconciliationFailuresV4(args) {
123668
124236
  );
123669
124237
  }
123670
124238
  const replayRows = /* @__PURE__ */ new Map();
123671
- for (const [id2, row] of replayStoredConstructionV4({
123672
- records: records2,
123673
- document: replayDocument,
123674
- replayContext,
123675
- valueId: reconciliation.valueId,
123676
- code: reconciliation.code,
123677
- member: owner,
123678
- ...compilationProject === void 0 ? {} : { compilationProject }
123679
- })) {
123680
- if (!isMemberValue(row)) {
123681
- throw new Error(
123682
- `Construction replay produced invalid value row "${id2}".`
123683
- );
124239
+ try {
124240
+ for (const [id2, row] of replayStoredConstructionV4({
124241
+ records: records2,
124242
+ document: replayDocument,
124243
+ replayContext,
124244
+ valueId: reconciliation.valueId,
124245
+ code: reconciliation.code,
124246
+ member: owner,
124247
+ ...compilationProject === void 0 ? {} : { compilationProject }
124248
+ })) {
124249
+ if (!isMemberValue(row)) {
124250
+ throw new Error(
124251
+ `Construction replay produced invalid value row "${id2}".`
124252
+ );
124253
+ }
124254
+ replayRows.set(id2, row);
124255
+ }
124256
+ } catch (error) {
124257
+ if (error instanceof NSGetterRuntimeError && error.source === void 0 && args.forceRecompile !== true) {
124258
+ return materializedInitializerReconciliationFailuresV4({
124259
+ ...args,
124260
+ forceRecompile: true,
124261
+ useBuildCaches: false
124262
+ });
123684
124263
  }
123685
- replayRows.set(id2, row);
124264
+ failures.push(
124265
+ initializerRuntimeDiagnostic(
124266
+ error,
124267
+ /* @__PURE__ */ new Map([[reconciliation.valueId, reconciliation.site]]),
124268
+ args.sourceTextByUri,
124269
+ args.analysis
124270
+ )
124271
+ );
124272
+ continue;
123686
124273
  }
123687
124274
  assertPulledConstructorsCompiled(replayDocument, replayContext);
123688
124275
  currentRows ??= new Map(currentDocument.values.map((row) => [row.id, row]));
@@ -124086,6 +124673,8 @@ function variantOwnershipRootRecords(records2) {
124086
124673
  var init_local_initializer_materialization = __esm({
124087
124674
  "src/project-source/local-initializer-materialization.ts"() {
124088
124675
  "use strict";
124676
+ init_NSGetterRuntimeError();
124677
+ init_runtime_diagnostic();
124089
124678
  init_evaluateInitializer();
124090
124679
  init_stored_closure_captures();
124091
124680
  init_initializer_dependencies();
@@ -124408,6 +124997,11 @@ function computeWorkspaceStatus(workspace, options) {
124408
124997
  if (positioned.severity === "error") parseErrors.push(positioned);
124409
124998
  else parseWarnings.push(positioned);
124410
124999
  }
125000
+ if (analysis.diagnostics.some(
125001
+ (diagnostic2) => diagnostic2.code === "duplicate-project-id" && diagnostic2.severity === "error"
125002
+ )) {
125003
+ return refusedStatus(conflictedFiles, parseErrors, parseWarnings);
125004
+ }
124411
125005
  const rootDeclaration = analysis.configuration.globals.find(
124412
125006
  (global) => global.type.name === "Root"
124413
125007
  );
@@ -124808,11 +125402,21 @@ function computeWorkspaceStatus(workspace, options) {
124808
125402
  variantValueBindingsV4(manifest, projectAnalysisV4),
124809
125403
  { analysis: projectAnalysisV4, registry: valueLowerRegistry }
124810
125404
  );
124811
- const supplementalRecords = options.lowerSupplementalRecords(
124812
- loweringRecords,
124813
- projectAnalysisV4,
124814
- options.trustedPendingProjectFiles
124815
- );
125405
+ let supplementalRecords;
125406
+ try {
125407
+ supplementalRecords = options.lowerSupplementalRecords(
125408
+ loweringRecords,
125409
+ projectAnalysisV4,
125410
+ options.trustedPendingProjectFiles
125411
+ );
125412
+ } catch (error) {
125413
+ parseErrors.push(sourceDiagnosticMapper(projectAnalysisV4)(error));
125414
+ return refusedStatus(
125415
+ conflictedFiles,
125416
+ uniqueSourceErrors(parseErrors),
125417
+ parseWarnings
125418
+ );
125419
+ }
124816
125420
  resolveRootPathDialogueGroupCollectionValuesV4({
124817
125421
  records: supplementalRecords,
124818
125422
  manifest,
@@ -125258,6 +125862,7 @@ function computeWorkspaceStatus(workspace, options) {
125258
125862
  authoredValueSeeds,
125259
125863
  reconciliations: valueLowerRegistry.initializerReconciliations,
125260
125864
  manifest,
125865
+ analysis: projectAnalysisV4,
125261
125866
  forceRecompile: options.forceRecompile,
125262
125867
  useBuildCaches: options.useBuildCaches,
125263
125868
  sourceTextByUri: new Map(
@@ -125373,7 +125978,7 @@ function computeWorkspaceStatus(workspace, options) {
125373
125978
  )
125374
125979
  );
125375
125980
  }
125376
- if (options.skipInitializerMaterialization !== true) {
125981
+ if (options.skipInitializerMaterialization !== true && !parseErrors.some(isBlockingSchemaSourceError)) {
125377
125982
  try {
125378
125983
  materializeWorkspaceStatusInitializers(
125379
125984
  workspace,
@@ -125381,14 +125986,23 @@ function computeWorkspaceStatus(workspace, options) {
125381
125986
  options.forceRecompile
125382
125987
  );
125383
125988
  } catch (error) {
125384
- const site = error instanceof StoredClosureCaptureError ? valueLowerRegistry.initializerSites.get(error.valueId) : void 0;
125385
- const position = site === void 0 ? { file: "<initializer-materialization>", line: 1, column: 1 } : composeReferenceSitePosition(site, sourceTextByUri);
125989
+ if (error instanceof NSGetterRuntimeError && error.source === void 0 && options.forceRecompile !== true) {
125990
+ try {
125991
+ materializeWorkspaceStatusInitializers(
125992
+ workspace,
125993
+ { changes, authoredValueSeeds },
125994
+ true
125995
+ );
125996
+ } catch (replayedError) {
125997
+ error = replayedError;
125998
+ }
125999
+ }
125386
126000
  parseErrors.push(
125387
- new SchemaSourceError(
125388
- error instanceof Error ? error.message : String(error),
125389
- position.file,
125390
- position.line,
125391
- position.column
126001
+ initializerRuntimeDiagnostic(
126002
+ error,
126003
+ valueLowerRegistry.initializerSites,
126004
+ sourceTextByUri,
126005
+ projectAnalysisV4
125392
126006
  )
125393
126007
  );
125394
126008
  }
@@ -125409,7 +126023,10 @@ function computeWorkspaceStatus(workspace, options) {
125409
126023
  analysis: projectAnalysisV4
125410
126024
  }
125411
126025
  },
125412
- localValues: valueLowerRegistry.localValues?.placements(),
126026
+ localValues: [
126027
+ ...valueLowerRegistry.localValues?.placements() ?? [],
126028
+ ...projectFilePlacementsV4(projectAnalysisV4)
126029
+ ],
125413
126030
  changes,
125414
126031
  conflictedFiles,
125415
126032
  parseErrors: uniqueSourceErrors(positionedErrors),
@@ -126461,12 +127078,13 @@ var ANIMATION_DOCUMENT_RECORD_KINDS, ANIMATION_REPLAY_RECORD_KINDS, PROSPECTIVE_
126461
127078
  var init_workspace_status_core = __esm({
126462
127079
  "src/project-source/workspace-status-core.ts"() {
126463
127080
  "use strict";
127081
+ init_runtime_diagnostic();
127082
+ init_NSGetterRuntimeError();
126464
127083
  init_lower_support();
126465
127084
  init_listener_lowering();
126466
127085
  init_inheritance_flags_validation();
126467
127086
  init_schema_source_validation();
126468
127087
  init_members();
126469
- init_stored_closure_captures();
126470
127088
  init_collection_binding_materialization();
126471
127089
  init_localizable_member_value_writes();
126472
127090
  init_localized_instance_override_writes();
@@ -128877,14 +129495,14 @@ function indexInitializerAuthoredRowIds(analysis, declaredConstructors2, classes
128877
129495
  continue;
128878
129496
  }
128879
129497
  const label = `${sourceClass.name}.${declaration.name}`;
128880
- const sources = initializerSourcesRequiringEvaluation(
129498
+ const sources2 = initializerSourcesRequiringEvaluation(
128881
129499
  declaration.initializer,
128882
129500
  declaration.type,
128883
129501
  declaredConstructors2,
128884
129502
  classesByName,
128885
129503
  sourceClass.name
128886
129504
  );
128887
- for (const source of sources) indexAuthoredRowIds(index, source, label);
129505
+ for (const source of sources2) indexAuthoredRowIds(index, source, label);
128888
129506
  }
128889
129507
  }
128890
129508
  return index;
@@ -136509,7 +137127,11 @@ function emitProjectDocumentSourcesV4(records2, options = {}) {
136509
137127
  const supplementalFiles = emitSupplementalProjectSourcesV4(
136510
137128
  records2,
136511
137129
  options.binaryPaths,
136512
- { manifest, collectionValuePaths: indexes3.rootPathsByValueId }
137130
+ {
137131
+ manifest,
137132
+ collectionValuePaths: indexes3.rootPathsByValueId,
137133
+ localValues: options.localValues
137134
+ }
136513
137135
  );
136514
137136
  const materializedConstructors = new Map(
136515
137137
  staticValues.materializedConstructors
@@ -136665,14 +137287,14 @@ var init_project_documents = __esm({
136665
137287
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync5, writeFileSync as writeFileSync6 } from "node:fs";
136666
137288
  import { createHash as createHash6 } from "node:crypto";
136667
137289
  import { dirname as dirname6, join as join5 } from "node:path";
136668
- function readProjectSourceAnalysisBuildCacheV4(root, sources) {
137290
+ function readProjectSourceAnalysisBuildCacheV4(root, sources2) {
136669
137291
  try {
136670
137292
  const parsed = JSON.parse(
136671
137293
  readFileSync6(join5(root, PROJECT_SOURCE_BUILD_CACHE_PATH), "utf8")
136672
137294
  );
136673
137295
  if (parsed === null || typeof parsed !== "object") return null;
136674
137296
  const cache = parsed;
136675
- if (cache.revision !== PROJECT_SOURCE_BUILD_CACHE_REVISION || cache.fingerprint !== projectSourceFingerprint(sources)) {
137297
+ if (cache.revision !== PROJECT_SOURCE_BUILD_CACHE_REVISION || cache.fingerprint !== projectSourceFingerprint(sources2)) {
136676
137298
  return null;
136677
137299
  }
136678
137300
  assertProjectSourceAnalysisV4(cache.analysis);
@@ -136681,12 +137303,12 @@ function readProjectSourceAnalysisBuildCacheV4(root, sources) {
136681
137303
  return null;
136682
137304
  }
136683
137305
  }
136684
- function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
137306
+ function writeProjectSourceAnalysisCacheV4(root, analysis, sources2) {
136685
137307
  const file = join5(root, PROJECT_SOURCE_ANALYSIS_V4_CACHE_PATH);
136686
137308
  mkdirSync6(dirname6(file), { recursive: true });
136687
137309
  writeFileSync6(file, `${JSON.stringify(analysis, null, 2)}
136688
137310
  `, "utf8");
136689
- if (sources === void 0) return;
137311
+ if (sources2 === void 0) return;
136690
137312
  const buildFile = join5(root, PROJECT_SOURCE_BUILD_CACHE_PATH);
136691
137313
  mkdirSync6(dirname6(buildFile), { recursive: true });
136692
137314
  const temporary = `${buildFile}.${process.pid}.tmp`;
@@ -136694,7 +137316,7 @@ function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
136694
137316
  temporary,
136695
137317
  `${JSON.stringify({
136696
137318
  revision: PROJECT_SOURCE_BUILD_CACHE_REVISION,
136697
- fingerprint: projectSourceFingerprint(sources),
137319
+ fingerprint: projectSourceFingerprint(sources2),
136698
137320
  analysis
136699
137321
  })}
136700
137322
  `,
@@ -136702,14 +137324,14 @@ function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
136702
137324
  );
136703
137325
  renameSync5(temporary, buildFile);
136704
137326
  }
136705
- function projectSourceFingerprint(sources) {
137327
+ function projectSourceFingerprint(sources2) {
136706
137328
  const hash = createHash6("sha256");
136707
137329
  hash.update(String(PROJECT_SOURCE_BUILD_CACHE_REVISION));
136708
137330
  hash.update("\0");
136709
137331
  hash.update(String(NEOSCRIPT_COMPILER_REVISION));
136710
137332
  hash.update("\0");
136711
137333
  hash.update(PROJECT_SCHEMA_CONTRACT.cliVersion);
136712
- for (const source of sources) {
137334
+ for (const source of sources2) {
136713
137335
  hash.update("\0");
136714
137336
  hash.update(source.kind);
136715
137337
  hash.update("\0");
@@ -136728,7 +137350,7 @@ var init_project_document_cache = __esm({
136728
137350
  init_registry();
136729
137351
  init_project_documents();
136730
137352
  PROJECT_SOURCE_BUILD_CACHE_PATH = ".neo/build/project-source-analysis-v4.json";
136731
- PROJECT_SOURCE_BUILD_CACHE_REVISION = 4;
137353
+ PROJECT_SOURCE_BUILD_CACHE_REVISION = 6;
136732
137354
  }
136733
137355
  });
136734
137356
 
@@ -136750,84 +137372,88 @@ function lowerProjectFileRegistrySourcesV4(root, state, analysis, options = {})
136750
137372
  const seenPaths = /* @__PURE__ */ new Map();
136751
137373
  const seenSymbols = /* @__PURE__ */ new Map();
136752
137374
  return declarations.map((declaration) => {
136753
- const normalizedPath = normalizeTrustedProjectBinaryPath(
136754
- declaration.path,
136755
- declaration.kind
136756
- );
136757
- const pathKey = normalizedPath.toLowerCase();
136758
- const previousPath = seenPaths.get(pathKey);
136759
- if (previousPath) {
136760
- throw new Error(
136761
- `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
136762
- );
136763
- }
136764
- seenPaths.set(pathKey, declaration);
136765
- const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
136766
- const previousSymbol = seenSymbols.get(symbolKey);
136767
- if (previousSymbol) {
136768
- throw new Error(
136769
- `Project file symbol ${declaration.symbol} is declared more than once.`
136770
- );
136771
- }
136772
- seenSymbols.set(symbolKey, declaration);
136773
- const baseState = state[`project-file:${declaration.recordId}`];
136774
- const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
136775
- const absolute = createWorkspaceSourcePathResolver(root)(normalizedPath);
136776
- const trustedPending = options.trustedPendingFiles?.get(
136777
- declaration.recordId
136778
- );
136779
- if (base === null && !existsSync5(absolute) && trustedPending === void 0) {
136780
- throw new Error(
136781
- `Pending project file ${declaration.symbol} is missing bytes at ${normalizedPath}.`
137375
+ try {
137376
+ const normalizedPath = normalizeTrustedProjectBinaryPath(
137377
+ declaration.path,
137378
+ declaration.kind
136782
137379
  );
136783
- }
136784
- const binary = existsSync5(absolute) ? inspectBinaryFile(absolute, normalizedPath) : trustedPending === void 0 ? null : {
136785
- path: normalizedPath,
136786
- kind: declaration.kind,
136787
- mimeType: trustedPending.mimeType,
136788
- byteLength: trustedPending.byteLength,
136789
- sha256: trustedPending.sha256
136790
- };
136791
- const now = Date.now();
136792
- const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
136793
- const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
136794
- const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
136795
- const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString3(baseSettings?.templateId) !== declaration.templateId;
136796
- const next = {
136797
- ...base ?? {},
136798
- id: declaration.recordId,
136799
- name: declaration.persistedName,
136800
- fileType: declaration.kind,
136801
- ...base === null && binary ? {
136802
- mimeType: binary.mimeType,
136803
- byteLength: binary.byteLength,
136804
- contentSha256: binary.sha256
136805
- } : {},
136806
- [otherSettingsField]: null
136807
- };
136808
- if (base === null) {
136809
- Object.assign(next, {
136810
- projectId: "",
136811
- status: "pending-upload",
136812
- storageKey: "",
136813
- storageETag: null,
136814
- createdAt: now,
136815
- updatedAt: now
136816
- });
136817
- }
136818
- if (importSettingsChanged) {
136819
- next[settingsField] = projectFileUnityImportSettingsV4(
136820
- declaration.kind,
136821
- declaration.templateId
137380
+ const pathKey = normalizedPath.toLowerCase();
137381
+ const previousPath = seenPaths.get(pathKey);
137382
+ if (previousPath) {
137383
+ throw new Error(
137384
+ `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
137385
+ );
137386
+ }
137387
+ seenPaths.set(pathKey, declaration);
137388
+ const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
137389
+ const previousSymbol = seenSymbols.get(symbolKey);
137390
+ if (previousSymbol) {
137391
+ throw new Error(
137392
+ `Project file symbol ${declaration.symbol} is declared more than once.`
137393
+ );
137394
+ }
137395
+ seenSymbols.set(symbolKey, declaration);
137396
+ const baseState = state[`project-file:${declaration.recordId}`];
137397
+ const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
137398
+ const absolute = createWorkspaceSourcePathResolver(root)(normalizedPath);
137399
+ const trustedPending = options.trustedPendingFiles?.get(
137400
+ declaration.recordId
136822
137401
  );
137402
+ if (base === null && !existsSync5(absolute) && trustedPending === void 0) {
137403
+ throw new Error(
137404
+ `Pending project file ${declaration.symbol} is missing bytes at ${normalizedPath}. Add the file there or correct the declaration path.`
137405
+ );
137406
+ }
137407
+ const binary = existsSync5(absolute) ? inspectBinaryFile(absolute, normalizedPath) : trustedPending === void 0 ? null : {
137408
+ path: normalizedPath,
137409
+ kind: declaration.kind,
137410
+ mimeType: trustedPending.mimeType,
137411
+ byteLength: trustedPending.byteLength,
137412
+ sha256: trustedPending.sha256
137413
+ };
137414
+ const now = Date.now();
137415
+ const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
137416
+ const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
137417
+ const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
137418
+ const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString3(baseSettings?.templateId) !== declaration.templateId;
137419
+ const next = {
137420
+ ...base ?? {},
137421
+ id: declaration.recordId,
137422
+ name: declaration.persistedName,
137423
+ fileType: declaration.kind,
137424
+ ...base === null && binary ? {
137425
+ mimeType: binary.mimeType,
137426
+ byteLength: binary.byteLength,
137427
+ contentSha256: binary.sha256
137428
+ } : {},
137429
+ [otherSettingsField]: null
137430
+ };
137431
+ if (base === null) {
137432
+ Object.assign(next, {
137433
+ projectId: "",
137434
+ status: "pending-upload",
137435
+ storageKey: "",
137436
+ storageETag: null,
137437
+ createdAt: now,
137438
+ updatedAt: now
137439
+ });
137440
+ }
137441
+ if (importSettingsChanged) {
137442
+ next[settingsField] = projectFileUnityImportSettingsV4(
137443
+ declaration.kind,
137444
+ declaration.templateId
137445
+ );
137446
+ }
137447
+ return {
137448
+ recordKind: "project-file",
137449
+ recordId: declaration.recordId,
137450
+ fileFields: next,
137451
+ file: declaration.sourceFile,
137452
+ line: declaration.sourceLine
137453
+ };
137454
+ } catch (error) {
137455
+ throw projectFileSourceError(error, declaration);
136823
137456
  }
136824
- return {
136825
- recordKind: "project-file",
136826
- recordId: declaration.recordId,
136827
- fileFields: next,
136828
- file: declaration.sourceFile,
136829
- line: declaration.sourceLine
136830
- };
136831
137457
  });
136832
137458
  }
136833
137459
  function lowerDiscoveredProjectBinariesV4(root, state, analysis) {
@@ -136839,7 +137465,7 @@ function lowerDiscoveredProjectBinariesV4(root, state, analysis) {
136839
137465
  return discoverProjectBinariesV4(
136840
137466
  root,
136841
137467
  explicit,
136842
- trackedProjectBinaryPaths(state)
137468
+ trackedProjectBinaryPaths(state, explicit)
136843
137469
  ).map((binary) => {
136844
137470
  const recordId = pendingDiscoveredFileId(binary.path);
136845
137471
  const templateId = binary.kind === "image" ? optionalString3(projectData.defaultTextureTemplateId) : optionalString3(projectData.defaultAudioClipTemplateId);
@@ -136908,7 +137534,7 @@ function inspectProjectBinaryStatusV4(root, state, analysis) {
136908
137534
  for (const binary of discoverProjectBinariesV4(
136909
137535
  root,
136910
137536
  explicit,
136911
- trackedProjectBinaryPaths(state)
137537
+ trackedProjectBinaryPaths(state, explicit)
136912
137538
  )) {
136913
137539
  result.push({
136914
137540
  fileId: pendingDiscoveredFileId(binary.path),
@@ -137027,9 +137653,10 @@ function normalizeSha256V4(value, owner) {
137027
137653
  }
137028
137654
  return value.toLowerCase();
137029
137655
  }
137030
- function trackedProjectBinaryPaths(state) {
137656
+ function trackedProjectBinaryPaths(state, explicit) {
137657
+ const declared = new Set(explicit.map((entry) => entry.recordId));
137031
137658
  return Object.values(state).flatMap(
137032
- (entry) => entry.recordKind === "project-file" && entry.projectBinary ? [entry.projectBinary.path] : []
137659
+ (entry) => entry.recordKind === "project-file" && entry.projectBinary && !declared.has(entry.recordId) ? [entry.projectBinary.path] : []
137033
137660
  );
137034
137661
  }
137035
137662
  function inspectBinaryFile(absolute, path) {
@@ -138060,6 +138687,218 @@ var init_status_output = __esm({
138060
138687
  }
138061
138688
  });
138062
138689
 
138690
+ // src/project-source/project-file-source-preservation.ts
138691
+ function applySourceEdits(source, edits, start = 0, end = source.length) {
138692
+ const chunks = [];
138693
+ let cursor = start;
138694
+ for (const edit of edits) {
138695
+ chunks.push(source.slice(cursor, edit.start), edit.text);
138696
+ cursor = edit.end;
138697
+ }
138698
+ chunks.push(source.slice(cursor, end));
138699
+ return chunks.join("");
138700
+ }
138701
+ function entryIdentity(entry) {
138702
+ if (entry.reference !== void 0) return `reference:${entry.reference}`;
138703
+ return entry.id === null ? null : `id:${entry.id}`;
138704
+ }
138705
+ function preserveProjectFileRegistrySource(existing, emitted, records2 = /* @__PURE__ */ new Map()) {
138706
+ if (!/\b(?:ImageRegistry|AudioClipRegistry)\b/.test(existing) || !/\b(?:ImageRegistry|AudioClipRegistry)\b/.test(emitted))
138707
+ return emitted;
138708
+ const templateIds = /* @__PURE__ */ new Map();
138709
+ for (const record6 of records2.values()) {
138710
+ if (!record6.deleted && (record6.recordKind === "unity-texture-template" || record6.recordKind === "unity-audio-clip-template") && isObjectRecord2(record6.data) && typeof record6.data.name === "string")
138711
+ templateIds.set(record6.data.name, record6.recordId);
138712
+ }
138713
+ const authoredDocument = parseNeoProjectSource(existing, "definition");
138714
+ const emittedDocument = parseNeoProjectSource(emitted, "definition");
138715
+ const authored = registries(authoredDocument, existing, templateIds);
138716
+ if (authored.length === 0) return emitted;
138717
+ const canonicalRegistries = registries(emittedDocument, emitted, templateIds);
138718
+ const authoredByName = new Map(
138719
+ authored.map((registry) => [registry.name, registry])
138720
+ );
138721
+ const canonicalNames = new Set(
138722
+ canonicalRegistries.map((registry) => registry.name)
138723
+ );
138724
+ const registryOnly = authoredDocument.declarations.length === authored.length && emittedDocument.declarations.length === canonicalRegistries.length && authored.every((registry) => canonicalNames.has(registry.name));
138725
+ const replacements = [];
138726
+ for (const canonical of canonicalRegistries) {
138727
+ const prior = authoredByName.get(canonical.name);
138728
+ if (prior === void 0) {
138729
+ if (registryOnly)
138730
+ replacements.push({
138731
+ start: existing.length,
138732
+ end: existing.length,
138733
+ text: "\n" + emitted.slice(canonical.start, canonical.end) + "\n"
138734
+ });
138735
+ continue;
138736
+ }
138737
+ const live = new Map(
138738
+ canonical.entries.map((entry) => [entryIdentity(entry), entry])
138739
+ );
138740
+ const bySymbol = new Map(
138741
+ canonical.entries.map((entry) => [entry.symbol, entry])
138742
+ );
138743
+ const edits = [];
138744
+ for (const entry of prior.entries) {
138745
+ const next = entryIdentity(entry) === null ? bySymbol.get(entry.symbol) : live.get(entryIdentity(entry));
138746
+ if (next !== void 0) live.delete(entryIdentity(next));
138747
+ if (next?.signature === entry.signature) {
138748
+ if (entry.id === null && next.id !== null) {
138749
+ const lineStart = existing.lastIndexOf("\n", entry.start - 1) + 1;
138750
+ const indentation = existing.slice(lineStart, entry.start).match(/^\s*/)?.[0] ?? "";
138751
+ const newline = existing.includes("\r\n") ? "\r\n" : "\n";
138752
+ edits.push({
138753
+ start: entry.start,
138754
+ end: entry.start,
138755
+ text: `@id(${JSON.stringify(next.id)})${newline}${indentation}`
138756
+ });
138757
+ }
138758
+ continue;
138759
+ }
138760
+ edits.push({
138761
+ start: entry.start,
138762
+ end: entry.end,
138763
+ text: next === void 0 ? "" : emitted.slice(next.start, next.end)
138764
+ });
138765
+ }
138766
+ if (live.size > 0) {
138767
+ const newline = existing.includes("\r\n") ? "\r\n" : "\n";
138768
+ const additions = [...live.values()].map(
138769
+ (entry) => emitted.slice(entry.start, entry.end).replace(/\r?\n/g, newline)
138770
+ ).join(newline + newline + " ");
138771
+ edits.push({
138772
+ start: prior.close,
138773
+ end: prior.close,
138774
+ text: ` ${additions}${newline}`
138775
+ });
138776
+ }
138777
+ const content = applySourceEdits(existing, edits, prior.start, prior.end);
138778
+ replacements.push({
138779
+ start: registryOnly ? prior.start : canonical.start,
138780
+ end: registryOnly ? prior.end : canonical.end,
138781
+ text: content
138782
+ });
138783
+ }
138784
+ if (registryOnly) emitted = existing;
138785
+ return applySourceEdits(
138786
+ emitted,
138787
+ replacements.sort((a, b) => a.start - b.start)
138788
+ );
138789
+ }
138790
+ function registries(document, source, templateIds) {
138791
+ if (document.diagnostics.some((diagnostic) => diagnostic.severity === "error"))
138792
+ return [];
138793
+ const result = [];
138794
+ for (const declaration of document.declarations) {
138795
+ if (declaration.kind !== "global" || !["ImageRegistry", "AudioClipRegistry"].includes(declaration.type.name))
138796
+ continue;
138797
+ const start = document.syntax.source.offsetAt(declaration.range.start);
138798
+ const end = document.syntax.source.offsetAt(declaration.range.end);
138799
+ const tokens = lex(source.slice(start, end)).tokens.filter(
138800
+ (token) => token.kind !== "comment" && token.kind !== "eof"
138801
+ );
138802
+ const open = tokens.findIndex((token) => token.text === "{");
138803
+ const close = tokens.findLast((token) => token.text === "}");
138804
+ if (open < 0 || close === void 0) continue;
138805
+ const entries = [];
138806
+ let first = open + 1;
138807
+ for (let index = first; index < tokens.length && tokens[index].start < close.start; index++) {
138808
+ if (tokens[index].text !== ";") continue;
138809
+ const entry = readEntry(
138810
+ tokens.slice(first, index + 1),
138811
+ start,
138812
+ templateIds
138813
+ );
138814
+ if (entry === null) return [];
138815
+ entries.push(entry);
138816
+ first = index + 1;
138817
+ }
138818
+ result.push({
138819
+ name: declaration.name,
138820
+ start,
138821
+ end,
138822
+ close: start + close.start,
138823
+ entries
138824
+ });
138825
+ }
138826
+ return result;
138827
+ }
138828
+ function readEntry(tokens, offset, templateIds) {
138829
+ const declaration = tokens.findIndex(
138830
+ (token) => token.text === "NeoImage" || token.text === "NeoAudioClip"
138831
+ );
138832
+ if (declaration < 0) return null;
138833
+ const idIndex = tokens.findIndex(
138834
+ (token, index) => token.text === "id" && tokens[index - 1]?.text === "@"
138835
+ );
138836
+ const id2 = idIndex < 0 ? null : tokens[idIndex + 2]?.value;
138837
+ if (id2 === void 0) return null;
138838
+ const initializer = tokens[declaration + 3];
138839
+ if (initializer !== void 0 && initializer.text !== "new") {
138840
+ if (tokens[declaration + 4]?.text !== ";") return null;
138841
+ return {
138842
+ id: null,
138843
+ reference: initializer.text,
138844
+ symbol: tokens[declaration + 1].text,
138845
+ start: offset + tokens[0].start,
138846
+ end: offset + tokens.at(-1).end,
138847
+ signature: JSON.stringify(
138848
+ tokens.slice(declaration).map((token) => token.text)
138849
+ )
138850
+ };
138851
+ }
138852
+ const path = tokens[declaration + 5]?.value;
138853
+ if (path === void 0) return null;
138854
+ let name = path.slice(path.lastIndexOf("/") + 1);
138855
+ let template = "null";
138856
+ const settingsIndex = tokens.findIndex(
138857
+ (token, index) => token.text === "settings" && tokens[index - 1]?.text === "@"
138858
+ );
138859
+ if (settingsIndex >= 0) {
138860
+ const settings = tokens.slice(settingsIndex + 1, declaration).map((token) => token.raw).join(" ");
138861
+ const expression = parseExpression(`new${settings}`);
138862
+ if (expression.kind !== "new") return null;
138863
+ for (let index = 0; index < expression.args.length; index++) {
138864
+ const argument2 = expression.args[index];
138865
+ if (expression.argumentNames?.[index] === "name" && argument2.kind === "litString")
138866
+ name = argument2.value;
138867
+ if (expression.argumentNames?.[index] === "template") {
138868
+ if (argument2.kind === "ident")
138869
+ template = templateIds.get(argument2.name) ?? argument2.name;
138870
+ else if (argument2.kind === "call" && argument2.callee.kind === "ident" && argument2.callee.name === "Reference") {
138871
+ const idArgument = argument2.args[argument2.argumentNames?.indexOf("id") ?? 0];
138872
+ if (idArgument?.kind !== "litString") return null;
138873
+ template = idArgument.value;
138874
+ } else if (argument2.kind !== "litNull") return null;
138875
+ }
138876
+ }
138877
+ }
138878
+ return {
138879
+ id: id2,
138880
+ symbol: tokens[declaration + 1].text,
138881
+ start: offset + tokens[0].start,
138882
+ end: offset + tokens.at(-1).end,
138883
+ signature: JSON.stringify([
138884
+ tokens[declaration].text,
138885
+ tokens[declaration + 1].text,
138886
+ path,
138887
+ name,
138888
+ template
138889
+ ])
138890
+ };
138891
+ }
138892
+ var init_project_file_source_preservation = __esm({
138893
+ "src/project-source/project-file-source-preservation.ts"() {
138894
+ "use strict";
138895
+ init_value_comparison();
138896
+ init_project_source_parser();
138897
+ init_lexer();
138898
+ init_executable_parser();
138899
+ }
138900
+ });
138901
+
138063
138902
  // src/project-source/source-layout.ts
138064
138903
  import { readFileSync as readFileSync10 } from "node:fs";
138065
138904
  import { relative as relative4, sep as sep4 } from "node:path";
@@ -138158,12 +138997,18 @@ function readWorkspaceLayoutStatusV4(workspace, document) {
138158
138997
  const content = readFileSync10(path, "utf8");
138159
138998
  if (neoProjectSourceKind(path, content) !== "definition") continue;
138160
138999
  const document2 = parseNeoProjectSource(content, "definition", path);
138161
- if (document2.declarations.some(
138162
- (declaration) => declaration.kind === "global" && isLocalValueGlobal(declaration)
138163
- )) {
138164
- throw new Error(
138165
- `Cannot recover local initializer placement before reset: ${error instanceof Error ? error.message : String(error)}`
138166
- );
139000
+ for (const declaration of document2.declarations) {
139001
+ if (declaration.kind !== "global") continue;
139002
+ if (isLocalValueGlobal(declaration)) {
139003
+ throw new Error(
139004
+ `Cannot recover local initializer placement before reset: ${error instanceof Error ? error.message : String(error)}`
139005
+ );
139006
+ }
139007
+ if (neoScriptRegistryReferenceType(declaration.type.name) !== null) {
139008
+ throw new Error(
139009
+ `Cannot recover project file declaration placement before reset: ${error instanceof Error ? error.message : String(error)}`
139010
+ );
139011
+ }
138167
139012
  }
138168
139013
  }
138169
139014
  return null;
@@ -145867,8 +146712,13 @@ function staticDelegate(target, parameters) {
145867
146712
  const list = parameters.join(", ");
145868
146713
  return `static (${list}) => ${target}(${list})`;
145869
146714
  }
145870
- function enumParser(enumName) {
145871
- return staticDelegate(`${enumName}.FromOptionId`, ["factoryOptionId"]);
146715
+ function enumOptionsName(ctx, enumId) {
146716
+ return ctx.enumOptionsNames.get(enumId) ?? "NeoEnumOptions";
146717
+ }
146718
+ function enumParser(ctx, enumId) {
146719
+ return staticDelegate(`${enumOptionsName(ctx, enumId)}.FromOptionId`, [
146720
+ "factoryOptionId"
146721
+ ]);
145872
146722
  }
145873
146723
  function immediateInvocationArguments(receiver, argumentNames) {
145874
146724
  const values = argumentNames.length === 1 ? [`(object?)(${argumentNames[0]})`] : argumentNames.length <= 4 ? argumentNames : [`new object?[] { ${argumentNames.join(", ")} }`];
@@ -145882,10 +146732,7 @@ function enumOptionFieldName(ctx, enumId, optionId) {
145882
146732
  const enumDef = ctx.exportData.enums[enumId];
145883
146733
  if (enumDef !== void 0) {
145884
146734
  const enumName = ctx.enumNames.get(enumId) ?? toCSharpTypeName(enumDef.name, "NeoEnum");
145885
- const taken = /* @__PURE__ */ new Set([
145886
- enumName,
145887
- ...GENERATED_ENUM_CLASS_MEMBER_NAMES
145888
- ]);
146735
+ const taken = /* @__PURE__ */ new Set(["value__"]);
145889
146736
  for (const key of getEnumOptionKeyOrder(enumDef)) {
145890
146737
  const option = enumDef.options[key];
145891
146738
  if (option === void 0) continue;
@@ -145898,7 +146745,7 @@ function enumOptionFieldName(ctx, enumId, optionId) {
145898
146745
  unique = `${preferred}_${ordinal}`;
145899
146746
  ctx.diagnostics.push({
145900
146747
  severity: "warning",
145901
- message: `Enum option "${option.name}" collides with a member the generated C# enum class "${enumName}" declares; using "${unique}".`,
146748
+ message: `Enum option "${option.name}" collides with another member of the generated C# enum "${enumName}"; using "${unique}".`,
145902
146749
  path: `enums.${enumId}.options.${key}`
145903
146750
  });
145904
146751
  }
@@ -145909,118 +146756,141 @@ function enumOptionFieldName(ctx, enumId, optionId) {
145909
146756
  }
145910
146757
  return names.get(optionId) ?? null;
145911
146758
  }
146759
+ function enumMemberValue(optionId) {
146760
+ let hash = 2166136261;
146761
+ for (let index = 0; index < optionId.length; index++) {
146762
+ hash = Math.imul(hash ^ optionId.charCodeAt(index), 16777619);
146763
+ }
146764
+ return (hash >>> 0) % 2147483647 + 1;
146765
+ }
145912
146766
  function generateEnumClass(ctx, enumId) {
145913
146767
  const enumDef = ctx.exportData.enums[enumId];
145914
146768
  if (enumDef === void 0) return [];
145915
146769
  if (sdkRuntimeEnumBinding(enumId) !== void 0) return [];
145916
146770
  const enumName = ctx.enumNames.get(enumId) ?? toCSharpTypeName(enumDef.name, "NeoEnum");
145917
- const optionKeyOrder = getEnumOptionKeyOrder(enumDef);
146771
+ const optionsName = enumOptionsName(ctx, enumId);
146772
+ const optionsByValue = /* @__PURE__ */ new Map();
146773
+ const options = getEnumOptionKeyOrder(enumDef).flatMap((optionId) => {
146774
+ const option = enumDef.options[optionId];
146775
+ if (option === void 0) {
146776
+ throw new Error(
146777
+ `Enum ${enumId} option order references missing option ${optionId}.`
146778
+ );
146779
+ }
146780
+ const fieldName = enumOptionFieldName(ctx, enumId, optionId);
146781
+ if (fieldName === null) {
146782
+ ctx.diagnostics.push({
146783
+ severity: "error",
146784
+ message: `Enum option name "${option.name}" is not a valid C# identifier.`,
146785
+ path: `enums.${enumId}.options.${optionId}`
146786
+ });
146787
+ return [];
146788
+ }
146789
+ const value = enumMemberValue(optionId);
146790
+ const clash = optionsByValue.get(value);
146791
+ if (clash !== void 0) {
146792
+ ctx.diagnostics.push({
146793
+ severity: "error",
146794
+ message: `Enum options "${clash}" and "${option.name}" generate the same C# enum value; recreate one of them to give it a new option id.`,
146795
+ path: `enums.${enumId}.options.${optionId}`
146796
+ });
146797
+ return [];
146798
+ }
146799
+ optionsByValue.set(value, option.name);
146800
+ return [
146801
+ {
146802
+ optionId,
146803
+ value,
146804
+ id: `"${escapeCSharpString(optionId)}"`,
146805
+ member: `${enumName}.${fieldName}`,
146806
+ fieldName,
146807
+ text: `"${escapeCSharpString(option.text)}"`
146808
+ }
146809
+ ];
146810
+ });
146811
+ const switchBody = (subject, arms, fallback) => [
146812
+ `return ${subject} switch`,
146813
+ "{",
146814
+ ...indent([...arms, `_ => ${fallback},`]),
146815
+ "};"
146816
+ ];
146817
+ const method2 = (signature, body) => [
146818
+ signature,
146819
+ "{",
146820
+ ...indent(body),
146821
+ "}"
146822
+ ];
145918
146823
  return [
145919
146824
  ...xmlSummary(ctx.docs.enums[enumId]),
145920
- `public sealed class ${enumName} : IEquatable<${enumName}>, global::NeoCompose.Runtime.INeoEnumOption`,
146825
+ `[NeoEnum(typeof(${optionsName}))]`,
146826
+ `public enum ${enumName}`,
146827
+ "{",
146828
+ ...indent(
146829
+ options.flatMap((option) => [
146830
+ ...xmlSummary(ctx.docs.enumOptions[option.optionId]),
146831
+ `${option.fieldName} = ${option.value},`
146832
+ ])
146833
+ ),
146834
+ "}",
146835
+ "",
146836
+ "/// <summary>",
146837
+ `/// Converts <see cref="${enumName}"/> to and from the option ids Neo stores.`,
146838
+ "/// </summary>",
146839
+ `public static class ${optionsName}`,
145921
146840
  "{",
145922
146841
  ...indent([
145923
- `private static readonly Dictionary<string, ${enumName}> values = new Dictionary<string, ${enumName}>();`,
145924
- "public string optionId { get; }",
145925
- "public string Text => TextForOptionId(optionId);",
145926
- "public string TextId => TextIdForOptionId(optionId);",
145927
- "",
145928
- `private ${enumName}(string optionId)`,
145929
- "{",
145930
- ...indent(["this.optionId = optionId;"]),
145931
- "}",
145932
- "",
145933
- ...optionKeyOrder.flatMap((optionId) => {
145934
- const option = enumDef.options[optionId];
145935
- if (option === void 0) {
145936
- throw new Error(
145937
- `Enum ${enumId} option order references missing option ${optionId}.`
145938
- );
145939
- }
145940
- const optionName = option.name;
145941
- const fieldName = enumOptionFieldName(ctx, enumId, optionId);
145942
- if (fieldName === null) {
145943
- ctx.diagnostics.push({
145944
- severity: "error",
145945
- message: `Enum option name "${optionName}" is not a valid C# identifier.`,
145946
- path: `enums.${enumId}.options.${optionId}`
145947
- });
145948
- return [`// Skipped invalid option id: ${optionId}`];
145949
- }
145950
- return [
145951
- ...xmlSummary(ctx.docs.enumOptions[optionId]),
145952
- `public static readonly ${enumName} ${fieldName} = FromOptionId("${escapeCSharpString(optionId)}");`
145953
- ];
145954
- }),
145955
- "",
145956
- `public static ${enumName} FromOptionId(string optionId)`,
145957
- "{",
145958
- ...indent([
145959
- "if (values.TryGetValue(optionId, out var known)) return known;",
145960
- `var created = new ${enumName}(optionId);`,
145961
- "values[optionId] = created;",
145962
- "return created;"
145963
- ]),
145964
- "}",
146842
+ ...method2(
146843
+ `public static ${enumName} FromOptionId(string optionId)`,
146844
+ switchBody(
146845
+ "optionId",
146846
+ options.map((option) => `${option.id} => ${option.member},`),
146847
+ `NeoUndeclaredEnumOptions<${enumName}>.FromOptionId(optionId)`
146848
+ )
146849
+ ),
145965
146850
  "",
145966
- `public static string[] ToOptionIds(IEnumerable<${enumName}>? options)`,
145967
- "{",
145968
- ...indent([
145969
- "if (options is null) return Array.Empty<string>();",
145970
- "var ids = new List<string>();",
145971
- "foreach (var option in options) ids.Add(option.optionId);",
145972
- "return ids.ToArray();"
145973
- ]),
145974
- "}",
146851
+ ...method2(
146852
+ `public static string OptionId(this ${enumName} value)`,
146853
+ switchBody(
146854
+ "value",
146855
+ options.map((option) => `${option.member} => ${option.id},`),
146856
+ `NeoUndeclaredEnumOptions<${enumName}>.OptionId(value)`
146857
+ )
146858
+ ),
145975
146859
  "",
145976
- "public static bool IsKnown(string id)",
145977
- "{",
145978
- ...indent([
145979
- "return id switch",
145980
- "{",
145981
- ...indent([
145982
- ...optionKeyOrder.map(
145983
- (optionId) => `"${escapeCSharpString(optionId)}" => true,`
145984
- ),
145985
- "_ => false,"
145986
- ]),
145987
- "};"
145988
- ]),
145989
- "}",
146860
+ ...method2(
146861
+ `public static string[] ToOptionIds(IEnumerable<${enumName}>? options)`,
146862
+ [
146863
+ "if (options is null) return Array.Empty<string>();",
146864
+ "var ids = new List<string>();",
146865
+ "foreach (var option in options) ids.Add(option.OptionId());",
146866
+ "return ids.ToArray();"
146867
+ ]
146868
+ ),
145990
146869
  "",
145991
- "public static string TextIdForOptionId(string optionId)",
145992
- "{",
145993
- ...indent([
145994
- "return optionId switch",
145995
- "{",
145996
- ...indent([
145997
- ...optionKeyOrder.flatMap((optionId) => {
145998
- const option = enumDef.options[optionId];
145999
- return [
146000
- `"${escapeCSharpString(optionId)}" => "${escapeCSharpString(option.text)}",`
146001
- ];
146002
- }),
146003
- "_ => optionId,"
146004
- ]),
146005
- "};"
146006
- ]),
146007
- "}",
146870
+ ...method2(
146871
+ "public static bool IsKnown(string optionId)",
146872
+ switchBody(
146873
+ "optionId",
146874
+ options.map((option) => `${option.id} => true,`),
146875
+ "false"
146876
+ )
146877
+ ),
146008
146878
  "",
146009
- "public static string TextForOptionId(string optionId, NeoClient? client = null)",
146010
- "{",
146011
- ...indent([
146012
- ctx.singleton ? `return (client ?? ${ctx.projectClassName}.RequireInstance().Client).Localization.ResolveText(TextIdForOptionId(optionId));` : "return client is null ? TextIdForOptionId(optionId) : client.Localization.ResolveText(TextIdForOptionId(optionId));"
146013
- ]),
146014
- "}",
146879
+ ...method2(
146880
+ `public static string TextId(this ${enumName} value)`,
146881
+ switchBody(
146882
+ "value",
146883
+ options.map((option) => `${option.member} => ${option.text},`),
146884
+ "value.OptionId()"
146885
+ )
146886
+ ),
146015
146887
  "",
146016
- `public static implicit operator string(${enumName} value) => value.optionId;`,
146017
- `public static implicit operator ${enumName}(string optionId) => FromOptionId(optionId);`,
146018
- "public override string ToString() => optionId;",
146019
- `public bool Equals(${enumName}? other) => other is not null && optionId == other.optionId;`,
146020
- `public override bool Equals(object? obj) => Equals(obj as ${enumName});`,
146021
- "public override int GetHashCode() => optionId.GetHashCode();",
146022
- `public static bool operator ==(${enumName}? left, ${enumName}? right) => ReferenceEquals(left, right) || (left is not null && left.Equals(right));`,
146023
- `public static bool operator !=(${enumName}? left, ${enumName}? right) => !(left == right);`
146888
+ ...method2(
146889
+ `public static string Text(this ${enumName} value, NeoClient? client = null)`,
146890
+ [
146891
+ ctx.singleton ? `return (client ?? ${ctx.projectClassName}.RequireInstance().Client).Localization.ResolveText(value.TextId());` : "return client is null ? value.TextId() : client.Localization.ResolveText(value.TextId());"
146892
+ ]
146893
+ )
146024
146894
  ]),
146025
146895
  "}"
146026
146896
  ];
@@ -146250,7 +147120,7 @@ function generateIndexedListWrappers(ctx, ownerClass, schemaKey, memberId, env)
146250
147120
  {
146251
147121
  definition: definition2,
146252
147122
  keyType: enumName,
146253
- codecs: enumKeyCodecArguments(indexedMember.enumId, enumName)
147123
+ codecs: enumKeyCodecArguments(ctx, indexedMember.enumId)
146254
147124
  }
146255
147125
  ];
146256
147126
  }
@@ -147520,7 +148390,7 @@ function generateExplicitInterfaceMember(ctx, schemaKey, memberId, interfaceName
147520
148390
  genericEnv,
147521
148391
  ownerClassId
147522
148392
  );
147523
- const setter = isMemberNSPropertyBase(member) && typeof member.setterCode === "string" && !suppressesInheritedSetter(interfaceContract) ? nsPropertyWriteExpression(schemaKey) : null;
148393
+ const setter = isMemberNSPropertyBase(member) && typeof member.setterCode === "string" && !suppressesInheritedSetter(interfaceContract) ? nsPropertyWriteExpression(ctx, member, schemaKey) : null;
147524
148394
  return [
147525
148395
  `${interfaceType2} ${interfaceName}.${memberName}`,
147526
148396
  "{",
@@ -148274,7 +149144,7 @@ function planWorldObjectEmission(ctx, classDef, worldKind) {
148274
149144
  // conversion runs through the wrapper's implicit operator, which is
148275
149145
  // where the required-member throw now lives.
148276
149146
  `Sprite ${SPRITE_OBJECT_VALUE_INTERFACE}.${schemaKeyToMemberName(SPRITE_OBJECT_SPRITE_SCHEMA_KEY)} => ${schemaKeyToMemberName(SPRITE_OBJECT_SPRITE_SCHEMA_KEY)};`,
148277
- `string ${SPRITE_OBJECT_VALUE_INTERFACE}.${schemaKeyToMemberName(SPRITE_OBJECT_MASK_INTERACTION_SCHEMA_KEY)} => ${schemaKeyToMemberName(SPRITE_OBJECT_MASK_INTERACTION_SCHEMA_KEY)}.optionId;`
149147
+ `string ${SPRITE_OBJECT_VALUE_INTERFACE}.${schemaKeyToMemberName(SPRITE_OBJECT_MASK_INTERACTION_SCHEMA_KEY)} => ${schemaKeyToMemberName(SPRITE_OBJECT_MASK_INTERACTION_SCHEMA_KEY)}.OptionId();`
148278
149148
  ],
148279
149149
  shadowedSchemaKeys: /* @__PURE__ */ new Set([
148280
149150
  SPRITE_OBJECT_SPRITE_SCHEMA_KEY,
@@ -148296,7 +149166,7 @@ function planSmartTileEmission(ctx, classDef) {
148296
149166
  return planValidatedContractEmission(ctx, classDef, worldKind, () => ({
148297
149167
  runtimeInterfaces: [SMART_TILE_INTERFACE],
148298
149168
  classMembers: [
148299
- `string ${SMART_TILE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_DEFAULT_COLLIDER_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_DEFAULT_COLLIDER_SCHEMA_KEY)}.optionId;`,
149169
+ `string ${SMART_TILE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_DEFAULT_COLLIDER_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_DEFAULT_COLLIDER_SCHEMA_KEY)}.OptionId();`,
148300
149170
  `IReadOnlyList<${SMART_TILE_RULE_INTERFACE}> ${SMART_TILE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULES_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULES_SCHEMA_KEY)};`
148301
149171
  ],
148302
149172
  shadowedSchemaKeys: /* @__PURE__ */ new Set([
@@ -148311,9 +149181,9 @@ function planSmartTileEmission(ctx, classDef) {
148311
149181
  classMembers: [
148312
149182
  `IReadOnlyList<${SMART_TILE_NEIGHBOR_INTERFACE}> ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_NEIGHBORS_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_NEIGHBORS_SCHEMA_KEY)};`,
148313
149183
  `IReadOnlyList<Sprite> ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_SPRITES_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_SPRITES_SCHEMA_KEY)};`,
148314
- `string ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_OUTPUT_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_OUTPUT_SCHEMA_KEY)}.optionId;`,
148315
- `string ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_COLLIDER_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_COLLIDER_SCHEMA_KEY)}.optionId;`,
148316
- `string ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_TRANSFORM_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_TRANSFORM_SCHEMA_KEY)}.optionId;`,
149184
+ `string ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_OUTPUT_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_OUTPUT_SCHEMA_KEY)}.OptionId();`,
149185
+ `string ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_COLLIDER_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_COLLIDER_SCHEMA_KEY)}.OptionId();`,
149186
+ `string ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_TRANSFORM_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_TRANSFORM_SCHEMA_KEY)}.OptionId();`,
148317
149187
  `float ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_MIN_ANIMATION_SPEED_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_MIN_ANIMATION_SPEED_SCHEMA_KEY)};`,
148318
149188
  `float ${SMART_TILE_RULE_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_MAX_ANIMATION_SPEED_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_RULE_MAX_ANIMATION_SPEED_SCHEMA_KEY)};`
148319
149189
  ],
@@ -148333,7 +149203,7 @@ function planSmartTileEmission(ctx, classDef) {
148333
149203
  runtimeInterfaces: [SMART_TILE_NEIGHBOR_INTERFACE],
148334
149204
  classMembers: [
148335
149205
  `Vector2Int ${SMART_TILE_NEIGHBOR_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_NEIGHBOR_CELL_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_NEIGHBOR_CELL_SCHEMA_KEY)}.Value;`,
148336
- `string ${SMART_TILE_NEIGHBOR_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_NEIGHBOR_CONDITION_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_NEIGHBOR_CONDITION_SCHEMA_KEY)}.optionId;`,
149206
+ `string ${SMART_TILE_NEIGHBOR_INTERFACE}.${schemaKeyToMemberName(WORLD_SMART_TILE_NEIGHBOR_CONDITION_SCHEMA_KEY)} => ${schemaKeyToMemberName(WORLD_SMART_TILE_NEIGHBOR_CONDITION_SCHEMA_KEY)}.OptionId();`,
148337
149207
  `string? ${SMART_TILE_NEIGHBOR_INTERFACE}.TileClassId => ${SMART_TILE_NEIGHBOR_TILE_RELATION_EXPRESSION};`
148338
149208
  ],
148339
149209
  shadowedSchemaKeys: /* @__PURE__ */ new Set([
@@ -149557,37 +150427,16 @@ function declaredConstructorParameters(ctx, classId, declared, genericEnv) {
149557
150427
  }
149558
150428
  identifiers.add(identifier2);
149559
150429
  const baseType = csharpFunctionTypeInfoValueType(ctx, argument2, true, genericEnv) ?? "object?";
149560
- const rendered = renderCSharpParameterDefault(
149561
- ctx,
149562
- argument2,
149563
- `constructors.${declared.id}.argumentTypes`
149564
- );
149565
- if (rendered === null) {
149566
- parameters.push({
149567
- declaredName: argument2.name,
149568
- identifier: identifier2,
149569
- csharpType: baseType,
149570
- defaultLiteral: null,
149571
- enumFallback: null
149572
- });
149573
- continue;
149574
- }
149575
- if (rendered.enumFallback === null) {
149576
- parameters.push({
149577
- declaredName: argument2.name,
149578
- identifier: identifier2,
149579
- csharpType: baseType,
149580
- defaultLiteral: rendered.literal,
149581
- enumFallback: null
149582
- });
149583
- continue;
149584
- }
149585
150430
  parameters.push({
149586
150431
  declaredName: argument2.name,
149587
150432
  identifier: identifier2,
149588
- csharpType: baseType.endsWith("?") ? baseType : `${baseType}?`,
149589
- defaultLiteral: rendered.literal,
149590
- enumFallback: rendered.enumFallback
150433
+ csharpType: baseType,
150434
+ defaultLiteral: renderCSharpParameterDefault(
150435
+ ctx,
150436
+ argument2,
150437
+ `constructors.${declared.id}.argumentTypes`
150438
+ ),
150439
+ scriptArgument: scriptValueExpression(ctx, argument2, identifier2)
149591
150440
  });
149592
150441
  }
149593
150442
  return parameters;
@@ -149661,10 +150510,9 @@ function generateDeclaredConstructor(ctx, classId, className, constructorId, par
149661
150510
  );
149662
150511
  const signatureParameters = [...declaredParameters, ...appendedParameters];
149663
150512
  const parameterText = ctx.singleton ? signatureParameters.join(", ") : [`${ctx.projectClassName} project`, ...signatureParameters].join(", ");
149664
- const argumentEntries = parameters.map((parameter4) => {
149665
- const valueExpression = parameter4.enumFallback === null ? parameter4.identifier : `${parameter4.identifier} ?? ${parameter4.enumFallback}`;
149666
- return `new global::NeoCompose.Runtime.NeoDeclaredConstructorArgument("${escapeCSharpString(parameter4.declaredName)}", ${valueExpression})`;
149667
- }).join(", ");
150513
+ const argumentEntries = parameters.map(
150514
+ (parameter4) => `new global::NeoCompose.Runtime.NeoDeclaredConstructorArgument("${escapeCSharpString(parameter4.declaredName)}", ${parameter4.scriptArgument})`
150515
+ ).join(", ");
149668
150516
  const argumentArray = argumentEntries.length === 0 ? "new global::NeoCompose.Runtime.NeoDeclaredConstructorArgument[] { }" : `new global::NeoCompose.Runtime.NeoDeclaredConstructorArgument[] { ${argumentEntries} }`;
149669
150517
  const suppliedValues = appendedMembers.map(
149670
150518
  (member) => `new global::NeoCompose.Runtime.NeoGeneratedConstructorValue("${escapeCSharpString(member.schemaKey)}", "${escapeCSharpString(member.memberId)}", ${member.identifier})`
@@ -149781,21 +150629,18 @@ function generateStaticFunctionMethod(ctx, ownerClass, member, genericEnv) {
149781
150629
  `members.${member.id}.argumentTypes`
149782
150630
  );
149783
150631
  const parameters = parameterList.map((parameter4) => parameter4.declaration).join(", ");
149784
- const coalesceStatements = parameterList.flatMap(
149785
- (parameter4) => parameter4.coalesceStatement === null ? [] : [parameter4.coalesceStatement]
150632
+ const argumentNames = parameterList.map((parameter4) => parameter4.identifier);
150633
+ const scriptArguments = parameterList.map(
150634
+ (parameter4) => parameter4.scriptArgument
149786
150635
  );
149787
- const argumentNames = parameterList.map(
149788
- (parameter4) => parameter4.valueExpression
149789
- );
149790
- const args = argumentNames.length === 0 ? "{ }" : `{ ${argumentNames.join(", ")} }`;
150636
+ const args = scriptArguments.length === 0 ? "{ }" : `{ ${scriptArguments.join(", ")} }`;
149791
150637
  const asyncModifier = member.kind === 23 /* NSFunction */ && callableIsDeferred(member) ? "async " : "";
149792
150638
  const declaration = `${memberAccessKeyword(member)} static ${asyncModifier}${returnType} ${functionMethodName(member.name)}(${parameters})`;
149793
150639
  const docs = xmlSummary(ctx.docs.members[member.id]);
149794
150640
  const stamp = memberMethodStamp(member);
149795
150641
  if (member.kind === 23 /* NSFunction */) {
149796
- const invocation = callableIsDeferred(member) ? `await node.InvokeStaticAsync(new object?[] ${args})` : `node.InvokeStatic(${immediateInvocationArguments(null, argumentNames)})`;
150642
+ const invocation = callableIsDeferred(member) ? `await node.InvokeStaticAsync(new object?[] ${args})` : `node.InvokeStatic(${immediateInvocationArguments(null, scriptArguments)})`;
149797
150643
  const body = [
149798
- ...coalesceStatements,
149799
150644
  `var client = ${ctx.projectClassName}.RequireInstance().Client;`,
149800
150645
  `var node = NeoMemberNSFunction.GetOrCreateStatic(client, "${escapeCSharpString(member.id)}");`,
149801
150646
  ...isFunctionVoidTypeInfo(member.returnTypeInfo) ? [`${invocation};`] : [
@@ -149832,7 +150677,6 @@ function generateStaticFunctionMethod(ctx, ownerClass, member, genericEnv) {
149832
150677
  declaration,
149833
150678
  "{",
149834
150679
  ...indent([
149835
- ...coalesceStatements,
149836
150680
  `if (${handler} is null)`,
149837
150681
  "{",
149838
150682
  ...indent([
@@ -150065,7 +150909,7 @@ function generateStaticNSProperty(ctx, member, memberName, memberId, genericEnv)
150065
150909
  ];
150066
150910
  const setter = typeof member.setterCode === "string" ? [
150067
150911
  ...prelude,
150068
- "var result = node.Set(value);",
150912
+ `var result = node.Set(${scriptValueExpression(ctx, member.returnTypeInfo, "value")});`,
150069
150913
  'if (!result.ok) throw new InvalidOperationException(result.error ?? "Static NSProperty setter failed.");'
150070
150914
  ] : null;
150071
150915
  return [
@@ -150235,7 +151079,7 @@ function generateMember(ctx, schemaKey, memberId, saved, modifier, emitBody, own
150235
151079
  genericEnv,
150236
151080
  ownerClassId
150237
151081
  );
150238
- const declaredPropertySetter = isMemberNSPropertyBase(member) && typeof member.setterCode === "string" ? nsPropertyWriteExpression(schemaKey) : null;
151082
+ const declaredPropertySetter = isMemberNSPropertyBase(member) && typeof member.setterCode === "string" ? nsPropertyWriteExpression(ctx, member, schemaKey) : null;
150239
151083
  const propertySetter = suppressesInheritedSetter(inheritedContract) ? null : declaredPropertySetter;
150240
151084
  const emitsStoredSetter = bindingContract === null ? memberSaved : bindingContract.hasSetter;
150241
151085
  const storedSetterBody = suppressesInheritedSetter(inheritedContract) || !emitsStoredSetter ? null : writeExpression(ctx, member, schemaKey, ownerClassName, ownerClassId);
@@ -150588,10 +151432,10 @@ function descendsFromWorldObject(ctx, classId) {
150588
151432
  }
150589
151433
  return false;
150590
151434
  }
150591
- function nsPropertyWriteExpression(schemaKey) {
151435
+ function nsPropertyWriteExpression(ctx, member, schemaKey) {
150592
151436
  const getNode = `writableNode.Get<NeoMemberNSProperty>("${escapeCSharpString(schemaKey)}")`;
150593
151437
  return [
150594
- `var result = ${getNode}.Set(valueId!, value);`,
151438
+ `var result = ${getNode}.Set(valueId!, ${scriptValueExpression(ctx, member.returnTypeInfo, "value")});`,
150595
151439
  'if (!result.ok) throw new InvalidOperationException(result.error ?? "NSProperty setter failed.");'
150596
151440
  ];
150597
151441
  }
@@ -150661,49 +151505,37 @@ function renderCSharpParameterDefault(ctx, argument2, path) {
150661
151505
  case 1 /* Bool */: {
150662
151506
  const defaultValue = argument2.defaultValue;
150663
151507
  if (defaultValue === void 0) return null;
150664
- if (defaultValue.value === null)
150665
- return { literal: "null", enumFallback: null };
150666
- return {
150667
- literal: defaultValue.value ? "true" : "false",
150668
- enumFallback: null
150669
- };
151508
+ if (defaultValue.value === null) return "null";
151509
+ return defaultValue.value ? "true" : "false";
150670
151510
  }
150671
151511
  case 2 /* Int */: {
150672
151512
  const defaultValue = argument2.defaultValue;
150673
151513
  if (defaultValue === void 0) return null;
150674
- if (defaultValue.value === null)
150675
- return { literal: "null", enumFallback: null };
150676
- return { literal: String(defaultValue.value), enumFallback: null };
151514
+ if (defaultValue.value === null) return "null";
151515
+ return String(defaultValue.value);
150677
151516
  }
150678
151517
  case 4 /* Float */: {
150679
151518
  const defaultValue = argument2.defaultValue;
150680
151519
  if (defaultValue === void 0) return null;
150681
- if (defaultValue.value === null)
150682
- return { literal: "null", enumFallback: null };
150683
- return { literal: `${String(defaultValue.value)}f`, enumFallback: null };
151520
+ if (defaultValue.value === null) return "null";
151521
+ return `${String(defaultValue.value)}f`;
150684
151522
  }
150685
151523
  case 20 /* Decimal */: {
150686
151524
  const defaultValue = argument2.defaultValue;
150687
151525
  if (defaultValue === void 0) return null;
150688
- if (defaultValue.value === null)
150689
- return { literal: "null", enumFallback: null };
150690
- return { literal: `${defaultValue.value}m`, enumFallback: null };
151526
+ if (defaultValue.value === null) return "null";
151527
+ return `${defaultValue.value}m`;
150691
151528
  }
150692
151529
  case 3 /* String */: {
150693
151530
  const defaultValue = argument2.defaultValue;
150694
151531
  if (defaultValue === void 0) return null;
150695
- if (defaultValue.value === null)
150696
- return { literal: "null", enumFallback: null };
150697
- return {
150698
- literal: `"${escapeCSharpString(defaultValue.value)}"`,
150699
- enumFallback: null
150700
- };
151532
+ if (defaultValue.value === null) return "null";
151533
+ return `"${escapeCSharpString(defaultValue.value)}"`;
150701
151534
  }
150702
151535
  case 8 /* Enum */: {
150703
151536
  const defaultValue = argument2.defaultValue;
150704
151537
  if (defaultValue === void 0) return null;
150705
- if (defaultValue.value === null)
150706
- return { literal: "null", enumFallback: null };
151538
+ if (defaultValue.value === null) return "null";
150707
151539
  const enumDef = ctx.exportData.enums[argument2.enumId];
150708
151540
  if (enumDef === void 0) {
150709
151541
  ctx.diagnostics.push({
@@ -150745,22 +151577,22 @@ function renderCSharpParameterDefault(ctx, argument2, path) {
150745
151577
  return null;
150746
151578
  }
150747
151579
  if (argument2.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
150748
- return {
150749
- literal: `NeoCompose.Runtime.NeoCellPatternExcluding.${schemaKeyToMemberName(option.name)}`,
150750
- enumFallback: null
150751
- };
150752
- return { literal: "null", enumFallback: `${enumName}.${accessor}` };
151580
+ return `NeoCompose.Runtime.NeoCellPatternExcluding.${schemaKeyToMemberName(option.name)}`;
151581
+ return `${enumName}.${accessor}`;
150753
151582
  }
150754
151583
  default: {
150755
151584
  if (argument2.defaultValue === void 0) return null;
150756
- return { literal: "null", enumFallback: null };
151585
+ return "null";
150757
151586
  }
150758
151587
  }
150759
151588
  }
151589
+ function scriptValueExpression(ctx, typeInfo, expression) {
151590
+ return typeInfo.type === 8 /* Enum */ && typeInfo.enumId !== NEO_CELL_PATTERN_EXCLUDING_ENUM_ID && ctx.enumNames.has(typeInfo.enumId) ? `${expression}${typeInfo.required ? "" : "?"}.OptionId()` : expression;
151591
+ }
150760
151592
  function renderFunctionParameters(ctx, member, genericBindingSaved, genericEnv, path) {
150761
151593
  return member.argumentTypes.map((argument2, index) => {
150762
151594
  const identifier2 = functionParameterName(argument2.name, index);
150763
- const baseType = csharpFunctionTypeInfoValueType(
151595
+ const type = csharpFunctionTypeInfoValueType(
150764
151596
  ctx,
150765
151597
  argument2,
150766
151598
  genericBindingSaved,
@@ -150768,10 +151600,9 @@ function renderFunctionParameters(ctx, member, genericBindingSaved, genericEnv,
150768
151600
  ) ?? "object?";
150769
151601
  const bare = {
150770
151602
  identifier: identifier2,
150771
- type: baseType,
150772
- declaration: `${baseType} ${identifier2}`,
150773
- valueExpression: identifier2,
150774
- coalesceStatement: null
151603
+ type,
151604
+ declaration: `${type} ${identifier2}`,
151605
+ scriptArgument: scriptValueExpression(ctx, argument2, identifier2)
150775
151606
  };
150776
151607
  if (callableIsDeferred(member) && parameterHasDefault(argument2)) {
150777
151608
  ctx.diagnostics.push({
@@ -150781,26 +151612,8 @@ function renderFunctionParameters(ctx, member, genericBindingSaved, genericEnv,
150781
151612
  });
150782
151613
  return bare;
150783
151614
  }
150784
- const rendered = renderCSharpParameterDefault(ctx, argument2, path);
150785
- if (rendered === null) return bare;
150786
- if (rendered.enumFallback === null) {
150787
- return {
150788
- identifier: identifier2,
150789
- type: baseType,
150790
- declaration: `${baseType} ${identifier2} = ${rendered.literal}`,
150791
- valueExpression: identifier2,
150792
- coalesceStatement: null
150793
- };
150794
- }
150795
- const nullableType = baseType.endsWith("?") ? baseType : `${baseType}?`;
150796
- const coalescedLocal = `${identifier2}Value`;
150797
- return {
150798
- identifier: identifier2,
150799
- type: nullableType,
150800
- declaration: `${nullableType} ${identifier2} = null`,
150801
- valueExpression: coalescedLocal,
150802
- coalesceStatement: `var ${coalescedLocal} = ${identifier2} ?? ${rendered.enumFallback};`
150803
- };
151615
+ const literal3 = renderCSharpParameterDefault(ctx, argument2, path);
151616
+ return literal3 === null ? bare : { ...bare, declaration: `${type} ${identifier2} = ${literal3}` };
150804
151617
  });
150805
151618
  }
150806
151619
  function functionSignature(ctx, member, genericEnv) {
@@ -150868,12 +151681,12 @@ function generateFunctionMethod(ctx, member, schemaKey, modifier = "", emitBody
150868
151681
  ];
150869
151682
  if (!emitBody) return [...docs, `${declaration};`];
150870
151683
  const stamp = memberMethodStamp(member);
150871
- const coalesceStatements = parameterList.flatMap(
150872
- (parameter4) => parameter4.coalesceStatement === null ? [] : [parameter4.coalesceStatement]
150873
- );
150874
- const argumentList = parameterList.map((parameter4) => parameter4.valueExpression).join(", ");
151684
+ const argumentList = parameterList.map((parameter4) => parameter4.identifier).join(", ");
150875
151685
  const call = `FunctionHandler.${methodName}(${argumentList})`;
150876
- const deferredArgs = member.argumentTypes.length === 0 ? "{ }" : `{ ${argumentList} }`;
151686
+ const scriptArguments = parameterList.map(
151687
+ (parameter4) => parameter4.scriptArgument
151688
+ );
151689
+ const deferredArgs = scriptArguments.length === 0 ? "{ }" : `{ ${scriptArguments.join(", ")} }`;
150877
151690
  const deferredCall = isFunctionVoidTypeInfo(member.returnTypeInfo) ? `return client.InvokeDeferredNativeFunction("${escapeCSharpString(member.id)}", this, new object?[] ${deferredArgs});` : `return client.InvokeDeferredNativeFunction<${csharpFunctionReturnType(ctx, member.returnTypeInfo, genericEnv, true)}>("${escapeCSharpString(member.id)}", this, new object?[] ${deferredArgs});`;
150878
151691
  if (member.id === NEO_TILE_CONVERT_MEMBER_ID) {
150879
151692
  return [
@@ -150881,10 +151694,7 @@ function generateFunctionMethod(ctx, member, schemaKey, modifier = "", emitBody
150881
151694
  ...stamp,
150882
151695
  declaration,
150883
151696
  "{",
150884
- ...indent([
150885
- ...coalesceStatements,
150886
- `return base.TryConvert(${argumentList});`
150887
- ]),
151697
+ ...indent([`return base.TryConvert(${argumentList});`]),
150888
151698
  "}"
150889
151699
  ];
150890
151700
  }
@@ -150895,7 +151705,6 @@ function generateFunctionMethod(ctx, member, schemaKey, modifier = "", emitBody
150895
151705
  declaration,
150896
151706
  "{",
150897
151707
  ...indent([
150898
- ...coalesceStatements,
150899
151708
  `var result = client.ScriptGridQueries.Invoke("${member.id}", this, new object?[] ${deferredArgs});`,
150900
151709
  ...nsGetterResultReturnExpression(
150901
151710
  ctx,
@@ -150911,11 +151720,10 @@ function generateFunctionMethod(ctx, member, schemaKey, modifier = "", emitBody
150911
151720
  if (isNSFunction2) {
150912
151721
  const invocation = callableIsDeferred(member) ? `writableNode.Get<NeoMemberNSFunction>("${escapeCSharpString(schemaKey)}").InvokeAsync(valueId!, new object?[] ${deferredArgs})` : `InvokeFunction(${immediateInvocationArguments(
150913
151722
  `"${escapeCSharpString(schemaKey)}"`,
150914
- parameterList.map((parameter4) => parameter4.valueExpression)
151723
+ scriptArguments
150915
151724
  )})`;
150916
151725
  const resultStatement = callableIsDeferred(member) ? `var result = await ${invocation};` : `var result = ${invocation};`;
150917
151726
  const body = [
150918
- ...coalesceStatements,
150919
151727
  ...callableIsDeferred(member) ? [
150920
151728
  `if (valueId is null) throw new InvalidOperationException("Cannot invoke NSFunction '${escapeCSharpString(member.name)}' without a backing receiver value id.");`
150921
151729
  ] : [],
@@ -150940,7 +151748,6 @@ function generateFunctionMethod(ctx, member, schemaKey, modifier = "", emitBody
150940
151748
  declaration,
150941
151749
  "{",
150942
151750
  ...indent([
150943
- ...coalesceStatements,
150944
151751
  ...callableIsDeferred(member) ? [deferredCall] : [
150945
151752
  "if (FunctionHandler is null)",
150946
151753
  "{",
@@ -151013,8 +151820,8 @@ function nativeFunctionArgExpression(ctx, typeInfo, valueExpression, argumentNam
151013
151820
  case 8 /* Enum */: {
151014
151821
  if (typeInfo.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
151015
151822
  return `NeoCellPatternStorage.${required2 ? "ReadExcluding" : "ReadOptionalExcluding"}(${valueExpression})`;
151016
- const enumName = ctx.enumNames.get(typeInfo.enumId) ?? "string";
151017
- return required2 ? `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(enumName)}) ?? throw new InvalidOperationException("Required enum argument '${escapeCSharpString(argumentName)}' could not be resolved.")` : `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(enumName)})`;
151823
+ const parser = enumParser(ctx, typeInfo.enumId);
151824
+ return required2 ? `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${parser}) ?? throw new InvalidOperationException("Required enum argument '${escapeCSharpString(argumentName)}' could not be resolved.")` : `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${parser})`;
151018
151825
  }
151019
151826
  case 18 /* DialogueLookup */: {
151020
151827
  const dialogueIds = `NeoGeneratedTypesSupport.ToStringArray(${valueExpression})`;
@@ -151053,8 +151860,7 @@ function nativeFunctionArgExpression(ctx, typeInfo, valueExpression, argumentNam
151053
151860
  if (isMemberEnumBase(binding) && isMultiSelectionMember(binding)) {
151054
151861
  if (binding.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
151055
151862
  return `NeoCellPatternStorage.ReadExcludingList(${valueExpression})`;
151056
- const enumName = ctx.enumNames.get(binding.enumId) ?? "string";
151057
- return `NeoGeneratedTypesSupport.ReadEnumList(${valueExpression}, ${enumParser(enumName)})`;
151863
+ return `NeoGeneratedTypesSupport.ReadEnumList(${valueExpression}, ${enumParser(ctx, binding.enumId)})`;
151058
151864
  }
151059
151865
  if (isMemberClassBase(binding)) {
151060
151866
  const typeName = ctx.typeNames.get(binding.classId);
@@ -151298,10 +152104,9 @@ function nsGetterResultReturnExpression(ctx, returnTypeInfo, valueExpression, sa
151298
152104
  ...prefix,
151299
152105
  `return NeoCellPatternStorage.${optional2 ? "ReadOptionalExcluding" : "ReadExcluding"}(${valueExpression});`
151300
152106
  ];
151301
- const enumName = ctx.enumNames.get(returnTypeInfo.enumId) ?? "string";
151302
152107
  return [
151303
152108
  ...prefix,
151304
- `var selected = NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(enumName)});`,
152109
+ `var selected = NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(ctx, returnTypeInfo.enumId)});`,
151305
152110
  optional2 ? "return selected;" : 'return selected ?? throw new InvalidOperationException("NSProperty returned null for a required enum value.");'
151306
152111
  ];
151307
152112
  }
@@ -151417,11 +152222,11 @@ function nsGetterGenericResultExpression(ctx, typeInfo, valueExpression, saved,
151417
152222
  if (isMemberEnumBase(binding)) {
151418
152223
  if (binding.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
151419
152224
  return `NeoCellPatternStorage.${isMultiSelectionMember(binding) ? "ReadExcludingList" : required2 ? "ReadExcluding" : "ReadOptionalExcluding"}(${valueExpression})`;
151420
- const enumName = ctx.enumNames.get(binding.enumId) ?? "string";
152225
+ const parser = enumParser(ctx, binding.enumId);
151421
152226
  if (isMultiSelectionMember(binding)) {
151422
- return `NeoGeneratedTypesSupport.ReadEnumList(${valueExpression}, ${enumParser(enumName)})`;
152227
+ return `NeoGeneratedTypesSupport.ReadEnumList(${valueExpression}, ${parser})`;
151423
152228
  }
151424
- const read = `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(enumName)})`;
152229
+ const read = `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${parser})`;
151425
152230
  return required2 ? `${read} ?? throw new InvalidOperationException("Required generic enum result was null.")` : read;
151426
152231
  }
151427
152232
  if (isMemberClassBase(binding)) {
@@ -151487,7 +152292,7 @@ function detachedReadExpression(ctx, member, schemaKey, saved, writeGuardMemberN
151487
152292
  "entryMemberId"
151488
152293
  );
151489
152294
  if (entryMember === null) return [];
151490
- const readEntry = detachedValueExpression(
152295
+ const readEntry2 = detachedValueExpression(
151491
152296
  ctx,
151492
152297
  entryMember,
151493
152298
  "entry",
@@ -151496,14 +152301,14 @@ function detachedReadExpression(ctx, member, schemaKey, saved, writeGuardMemberN
151496
152301
  genericEnv
151497
152302
  );
151498
152303
  const entryType = csharpValueType(ctx, entryMember, saved, genericEnv);
151499
- if (readEntry === null || entryType === null) return [];
152304
+ if (readEntry2 === null || entryType === null) return [];
151500
152305
  const guardArguments = writeGuardMemberName == null ? "" : `, () => ThrowIfReadOnly("${escapeCSharpString(writeGuardMemberName)}"), () => IsReadOnly`;
151501
152306
  const [viewType, view] = saved ? [
151502
152307
  `NeoList<${entryType}>`,
151503
- `DetachedList<${entryType}>("${key}", entry => ${readEntry}, (client, child) => ${childConverter(ctx, entryMember, true, writeGuardMemberName != null, genericEnv)}, item => ${serializeItemExpression(ctx, entryMember, "item")}${guardArguments})`
152308
+ `DetachedList<${entryType}>("${key}", entry => ${readEntry2}, (client, child) => ${childConverter(ctx, entryMember, true, writeGuardMemberName != null, genericEnv)}, item => ${serializeItemExpression(ctx, entryMember, "item")}${guardArguments})`
151504
152309
  ] : [
151505
152310
  `NeoReadOnlyList<${entryType}>`,
151506
- `DetachedReadOnlyList<${entryType}>("${key}", entry => ${readEntry}, (client, child) => ${childConverter(ctx, entryMember, false, false, genericEnv)})`
152311
+ `DetachedReadOnlyList<${entryType}>("${key}", entry => ${readEntry2}, (client, child) => ${childConverter(ctx, entryMember, false, false, genericEnv)})`
151507
152312
  ];
151508
152313
  return [
151509
152314
  `if (TryReadDetached("${key}", out _))`,
@@ -151571,11 +152376,11 @@ function detachedValueExpression(ctx, member, value, required2, saved, genericEn
151571
152376
  if (isMemberEnumBase(member)) {
151572
152377
  if (member.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
151573
152378
  return `NeoCellPatternStorage.${isMultiSelectionMember(member) ? "ReadExcludingList" : required2 ? "ReadExcluding" : "ReadOptionalExcluding"}(${value})`;
151574
- const enumName = ctx.enumNames.get(member.enumId);
151575
- if (enumName === void 0) return null;
152379
+ if (!ctx.enumNames.has(member.enumId)) return null;
152380
+ const parser = enumParser(ctx, member.enumId);
151576
152381
  if (isMultiSelectionMember(member))
151577
- return `NeoGeneratedTypesSupport.ReadEnumList(${value}, ${enumParser(enumName)})`;
151578
- const read = `NeoGeneratedTypesSupport.ReadEnumSingle(${value}, ${enumParser(enumName)})`;
152382
+ return `NeoGeneratedTypesSupport.ReadEnumList(${value}, ${parser})`;
152383
+ const read = `NeoGeneratedTypesSupport.ReadEnumSingle(${value}, ${parser})`;
151579
152384
  return required2 ? `${read} ?? throw new InvalidOperationException("Required enum '${escapeCSharpString(member.name)}' has no selected option.")` : `${value} is null ? null : ${read}`;
151580
152385
  }
151581
152386
  if (isMemberClassBase(member) && member.classArguments == null) {
@@ -151744,15 +152549,15 @@ function storedReadExpression(ctx, member, schemaKey, nodeType, saved, ownerClas
151744
152549
  return [
151745
152550
  `return NeoCellPatternStorage.${isMultiSelectionMember(member) ? "ReadExcludingList" : isRequiredMember2(member) ? "ReadExcluding" : "ReadOptionalExcluding"}(${getNode}.Selected());`
151746
152551
  ];
151747
- const enumName = ctx.enumNames.get(member.enumId) ?? "string";
152552
+ const optionsName = enumOptionsName(ctx, member.enumId);
151748
152553
  return isMultiSelectionMember(member) ? [
151749
- `return NeoGeneratedTypesSupport.ReadEnumList(${getNode}.Selected(), ${enumParser(enumName)});`
152554
+ `return NeoGeneratedTypesSupport.ReadEnumList(${getNode}.Selected(), ${enumParser(ctx, member.enumId)});`
151750
152555
  ] : isRequiredMember2(member) ? [
151751
152556
  `var selected = NeoGeneratedTypesSupport.ReadSingleSelected(${getNode});`,
151752
- `return selected is null ? throw new InvalidOperationException("Required enum '${escapeCSharpString(schemaKey)}' has no selected option.") : ${enumName}.FromOptionId(selected);`
152557
+ `return selected is null ? throw new InvalidOperationException("Required enum '${escapeCSharpString(schemaKey)}' has no selected option.") : ${optionsName}.FromOptionId(selected);`
151753
152558
  ] : [
151754
152559
  `var selected = NeoGeneratedTypesSupport.ReadSingleSelected(${getNode});`,
151755
- `return selected is null ? null : ${enumName}.FromOptionId(selected);`
152560
+ `return selected is null ? null : ${optionsName}.FromOptionId(selected);`
151756
152561
  ];
151757
152562
  }
151758
152563
  if (isMemberLookupBase(member)) {
@@ -151909,13 +152714,12 @@ function writeExpression(ctx, member, schemaKey, ownerClassName, ownerClassId) {
151909
152714
  return [
151910
152715
  `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(NeoCellPatternStorage.${isMultiSelectionMember(member) ? "ExcludingListIds" : "OptionalExcludingIds"}(value)));`
151911
152716
  ];
151912
- const enumName = ctx.enumNames.get(member.enumId) ?? "string";
151913
152717
  return isMultiSelectionMember(member) ? [
151914
- `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(${enumName}.ToOptionIds(value)));`
152718
+ `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(${enumOptionsName(ctx, member.enumId)}.ToOptionIds(value)));`
151915
152719
  ] : isRequiredMember2(member) ? [
151916
- `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(new[] { value.optionId }));`
152720
+ `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(new[] { value.OptionId() }));`
151917
152721
  ] : [
151918
- `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(value is null ? null : new[] { value.optionId }));`
152722
+ `NeoGeneratedTypesSupport.SetValue(writableNode, "${key}", NeoGeneratedTypesSupport.Value(value is null ? null : new[] { value.Value.OptionId() }));`
151919
152723
  ];
151920
152724
  }
151921
152725
  if (isMemberLookupBase(member)) {
@@ -152030,14 +152834,13 @@ function collectionReadExpression(ctx, member, getNode, schemaKey, entryMember,
152030
152834
  `new ${collectionType}(client, memberNode, (client, child) => ${childConverter(ctx, entryMember, false, false, genericEnv)}${keyCodecArguments})`
152031
152835
  );
152032
152836
  }
152033
- function enumKeyCodecArguments(enumId, enumName) {
152034
- return enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID ? ", id => NeoCellPatternStorage.ReadExcluding(new[] { id }), key => NeoCellPatternStorage.ExcludingIds(key)[0]" : `, ${enumParser(enumName)}, key => key.optionId`;
152837
+ function enumKeyCodecArguments(ctx, enumId) {
152838
+ return enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID ? ", id => NeoCellPatternStorage.ReadExcluding(new[] { id }), key => NeoCellPatternStorage.ExcludingIds(key)[0]" : `, ${enumParser(ctx, enumId)}, key => key.OptionId()`;
152035
152839
  }
152036
152840
  function dictionaryKeyCodecArguments(ctx, member) {
152037
152841
  if (!isEnumKeyedDictionaryMember(member)) return "";
152038
- const keyEnumName = ctx.enumNames.get(member.keyEnumId);
152039
- if (keyEnumName === void 0) return "";
152040
- return enumKeyCodecArguments(member.keyEnumId, keyEnumName);
152842
+ if (!ctx.enumNames.has(member.keyEnumId)) return "";
152843
+ return enumKeyCodecArguments(ctx, member.keyEnumId);
152041
152844
  }
152042
152845
  function serializeItemExpression(ctx, member, valueExpression) {
152043
152846
  if (member === null) return valueExpression;
@@ -152046,7 +152849,7 @@ function serializeItemExpression(ctx, member, valueExpression) {
152046
152849
  if (isMemberEnumBase(member)) {
152047
152850
  if (member.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
152048
152851
  return `NeoGeneratedTypesSupport.Value(NeoCellPatternStorage.${isMultiSelectionMember(member) ? "ExcludingListIds" : "OptionalExcludingIds"}(${valueExpression}))`;
152049
- const rawExpression = isMultiSelectionMember(member) ? `${ctx.enumNames.get(member.enumId) ?? "NeoEnum"}.ToOptionIds(${valueExpression})` : `${valueExpression} is null ? null : new[] { ${valueExpression}.optionId }`;
152852
+ const rawExpression = isMultiSelectionMember(member) ? `${enumOptionsName(ctx, member.enumId)}.ToOptionIds(${valueExpression})` : `${valueExpression} is ${ctx.enumNames.get(member.enumId) ?? "NeoEnum"} enumOption ? new[] { enumOption.OptionId() } : null`;
152050
152853
  return `NeoGeneratedTypesSupport.Value(${rawExpression})`;
152051
152854
  }
152052
152855
  if (isMemberLookupBase(member)) {
@@ -152235,8 +153038,8 @@ function childConverter(ctx, member, saved = false, branchOnReadOnly = false, ge
152235
153038
  if (isMemberEnumBase(member)) {
152236
153039
  if (member.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
152237
153040
  return `NeoCellPatternStorage.${isMultiSelectionMember(member) ? "ReadExcludingList" : isRequiredMember2(member) ? "ReadExcluding" : "ReadOptionalExcluding"}(((NeoMemberEnum)child).Selected())`;
152238
- const enumName = ctx.enumNames.get(member.enumId) ?? "string";
152239
- return isMultiSelectionMember(member) ? `NeoGeneratedTypesSupport.ReadEnumList(((NeoMemberEnum)child).Selected(), ${enumParser(enumName)})` : isRequiredMember2(member) ? `NeoGeneratedTypesSupport.ReadEnumSingle(((NeoMemberEnum)child).Selected(), ${enumParser(enumName)}) ?? throw new InvalidOperationException("Required enum '${escapeCSharpString(member.name)}' has no selected option.")` : `NeoGeneratedTypesSupport.ReadEnumSingle(((NeoMemberEnum)child).Selected(), ${enumParser(enumName)})`;
153041
+ const parser = enumParser(ctx, member.enumId);
153042
+ return isMultiSelectionMember(member) ? `NeoGeneratedTypesSupport.ReadEnumList(((NeoMemberEnum)child).Selected(), ${parser})` : isRequiredMember2(member) ? `NeoGeneratedTypesSupport.ReadEnumSingle(((NeoMemberEnum)child).Selected(), ${parser}) ?? throw new InvalidOperationException("Required enum '${escapeCSharpString(member.name)}' has no selected option.")` : `NeoGeneratedTypesSupport.ReadEnumSingle(((NeoMemberEnum)child).Selected(), ${parser})`;
152240
153043
  }
152241
153044
  if (isMemberLookupBase(member)) return "((NeoMemberLookup)child).Selected()";
152242
153045
  if (isMemberDialogueLookupBase(member))
@@ -152290,8 +153093,8 @@ function nsGetterChildConverter(ctx, returnTypeInfo, saved, genericEnv) {
152290
153093
  case 8 /* Enum */: {
152291
153094
  if (returnTypeInfo.enumId === NEO_CELL_PATTERN_EXCLUDING_ENUM_ID)
152292
153095
  return `NeoCellPatternStorage.${returnTypeInfo.required ? "ReadExcluding" : "ReadOptionalExcluding"}(${valueExpression})`;
152293
- const enumName = ctx.enumNames.get(returnTypeInfo.enumId) ?? "string";
152294
- return `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(enumName)})`;
153096
+ const readExpression2 = `NeoGeneratedTypesSupport.ReadEnumSingle(${valueExpression}, ${enumParser(ctx, returnTypeInfo.enumId)})`;
153097
+ return returnTypeInfo.required ? `${readExpression2} ?? throw new InvalidOperationException("NSProperty returned null for a required enum value.")` : readExpression2;
152295
153098
  }
152296
153099
  case 18 /* DialogueLookup */: {
152297
153100
  const dialogueIds = `NeoGeneratedTypesSupport.ToStringArray(${valueExpression})`;
@@ -152358,7 +153161,7 @@ function escapeCSharpString(value) {
152358
153161
  (character) => CSHARP_STRING_ESCAPES.get(character) ?? `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`
152359
153162
  );
152360
153163
  }
152361
- var NEO_MEMORY_SCHEMA_KEYS, NEO_MEMORY_CLASS_NAMES, GENERATED_ENUM_CLASS_MEMBER_NAMES, CHILDREN_SCHEMA_KEY, TILE_LAYER_LINK_INTERFACE, OBJECT_LAYER_LINK_INTERFACE, OBJECT_SPAWN_HOOKS_INTERFACE, TILE_LAYER_RENDER_TARGET_PROVIDER_INTERFACE, SMART_TILE_SOURCE_INTERFACE, SMART_TILE_INTERFACE, SMART_TILE_RULE_INTERFACE, SMART_TILE_NEIGHBOR_INTERFACE, WORLD_OBJECT_VALUE_INTERFACE, OBJECT_COMPOSITION_SOURCE_INTERFACE, COLLIDER_SOURCE_INTERFACE, SORTING_GROUP_SOURCE_INTERFACE, COLLIDER_INTERFACE, SORTING_GROUP_INTERFACE, SPRITE_OBJECT_VALUE_INTERFACE, SORTING_GROUP_SOURCE_MEMBER_NAME, OBJECT_BASE_NAME_SCHEMA_KEY, OBJECT_BASE_POSITION_SCHEMA_KEY, OBJECT_BASE_SIZE_SCHEMA_KEY, SPRITE_OBJECT_SPRITE_SCHEMA_KEY, SPRITE_OBJECT_FLIP_X_SCHEMA_KEY, SPRITE_OBJECT_FLIP_Y_SCHEMA_KEY, SPRITE_OBJECT_MASK_INTERACTION_SCHEMA_KEY, SPRITE_OBJECT_SORTING_ORDER_SCHEMA_KEY, TILE_CELL_SCHEMA_KEY, EMPTY_RUNTIME_CONTRACT_PLAN, SMART_TILE_CONTRACT_WORLD_KINDS, WORLD_OBJECT_CONTRACT_WORLD_KINDS, SMART_TILE_NEIGHBOR_TILE_RELATION_EXPRESSION, REQUIRED_STRING_CONTRACT_FIELD, REQUIRED_BOOL_CONTRACT_FIELD, OPTIONAL_BOOL_CONTRACT_FIELD, OPTIONAL_INT_CONTRACT_FIELD, REQUIRED_FLOAT_CONTRACT_FIELD, REQUIRED_SPRITE_CONTRACT_FIELD, REQUIRED_VECTOR2_CONTRACT_FIELD, OPTIONAL_VECTOR2_CONTRACT_FIELD, REQUIRED_VECTOR2_INT_CONTRACT_FIELD, REQUIRED_VECTOR3_CONTRACT_FIELD, VARIANTS_SCOPE_NAME, BASE_VARIANT_NAME, VARIANT_APPLY_METHOD_NAME, VARIANT_FOLDER_SEPARATOR, CSHARP_STRING_ESCAPES, UNRESOLVED_VARIANT_TARGET;
153164
+ var NEO_MEMORY_SCHEMA_KEYS, NEO_MEMORY_CLASS_NAMES, CHILDREN_SCHEMA_KEY, TILE_LAYER_LINK_INTERFACE, OBJECT_LAYER_LINK_INTERFACE, OBJECT_SPAWN_HOOKS_INTERFACE, TILE_LAYER_RENDER_TARGET_PROVIDER_INTERFACE, SMART_TILE_SOURCE_INTERFACE, SMART_TILE_INTERFACE, SMART_TILE_RULE_INTERFACE, SMART_TILE_NEIGHBOR_INTERFACE, WORLD_OBJECT_VALUE_INTERFACE, OBJECT_COMPOSITION_SOURCE_INTERFACE, COLLIDER_SOURCE_INTERFACE, SORTING_GROUP_SOURCE_INTERFACE, COLLIDER_INTERFACE, SORTING_GROUP_INTERFACE, SPRITE_OBJECT_VALUE_INTERFACE, SORTING_GROUP_SOURCE_MEMBER_NAME, OBJECT_BASE_NAME_SCHEMA_KEY, OBJECT_BASE_POSITION_SCHEMA_KEY, OBJECT_BASE_SIZE_SCHEMA_KEY, SPRITE_OBJECT_SPRITE_SCHEMA_KEY, SPRITE_OBJECT_FLIP_X_SCHEMA_KEY, SPRITE_OBJECT_FLIP_Y_SCHEMA_KEY, SPRITE_OBJECT_MASK_INTERACTION_SCHEMA_KEY, SPRITE_OBJECT_SORTING_ORDER_SCHEMA_KEY, TILE_CELL_SCHEMA_KEY, EMPTY_RUNTIME_CONTRACT_PLAN, SMART_TILE_CONTRACT_WORLD_KINDS, WORLD_OBJECT_CONTRACT_WORLD_KINDS, SMART_TILE_NEIGHBOR_TILE_RELATION_EXPRESSION, REQUIRED_STRING_CONTRACT_FIELD, REQUIRED_BOOL_CONTRACT_FIELD, OPTIONAL_BOOL_CONTRACT_FIELD, OPTIONAL_INT_CONTRACT_FIELD, REQUIRED_FLOAT_CONTRACT_FIELD, REQUIRED_SPRITE_CONTRACT_FIELD, REQUIRED_VECTOR2_CONTRACT_FIELD, OPTIONAL_VECTOR2_CONTRACT_FIELD, REQUIRED_VECTOR2_INT_CONTRACT_FIELD, REQUIRED_VECTOR3_CONTRACT_FIELD, VARIANTS_SCOPE_NAME, BASE_VARIANT_NAME, VARIANT_APPLY_METHOD_NAME, VARIANT_FOLDER_SEPARATOR, CSHARP_STRING_ESCAPES, UNRESOLVED_VARIANT_TARGET;
152362
153165
  var init_generate_unity_classes = __esm({
152363
153166
  "../src/models/exports/unity-codegen/generate-unity-classes.ts"() {
152364
153167
  "use strict";
@@ -152404,24 +153207,6 @@ var init_generate_unity_classes = __esm({
152404
153207
  textNodeMemory: "NeoTextNodeMemory",
152405
153208
  choiceLog: "NeoChoiceLog"
152406
153209
  };
152407
- GENERATED_ENUM_CLASS_MEMBER_NAMES = /* @__PURE__ */ new Set([
152408
- "values",
152409
- "optionId",
152410
- "Text",
152411
- "TextId",
152412
- "FromOptionId",
152413
- "ToOptionIds",
152414
- "IsKnown",
152415
- "TextIdForOptionId",
152416
- "TextForOptionId",
152417
- "ToString",
152418
- "Equals",
152419
- "GetHashCode",
152420
- "GetType",
152421
- "ReferenceEquals",
152422
- "MemberwiseClone",
152423
- "Finalize"
152424
- ]);
152425
153210
  CHILDREN_SCHEMA_KEY = "Children";
152426
153211
  TILE_LAYER_LINK_INTERFACE = "INeoTileLayerLinkValue";
152427
153212
  OBJECT_LAYER_LINK_INTERFACE = "INeoObjectLayerLinkValue";
@@ -155679,7 +156464,7 @@ var UNITY_CODEGEN_REVISION;
155679
156464
  var init_unity_codegen_revision = __esm({
155680
156465
  "../src/models/exports/unity-codegen-revision.ts"() {
155681
156466
  "use strict";
155682
- UNITY_CODEGEN_REVISION = "03bfbde6bcc46538292d7c03998e5a684d7a0a0bff8959f9aeaa8a38d18753f5";
156467
+ UNITY_CODEGEN_REVISION = "30d96d89e3d179ccf291e522834281f90dbd44b464cf51eea76aae87eed97e7e";
155683
156468
  }
155684
156469
  });
155685
156470
 
@@ -156312,15 +157097,19 @@ async function readExportSource(workspace, identityLedger, onPhase) {
156312
157097
  onPhase
156313
157098
  });
156314
157099
  const prepared = candidate.preparedLocal;
157100
+ const { status } = candidate;
157101
+ const declaredPaths = new Map(
157102
+ (status.binaryFiles ?? []).map((binary) => [binary.fileId, binary.path])
157103
+ );
156315
157104
  if (prepared === null) {
156316
157105
  return {
156317
157106
  records: workspace.state.records,
156318
157107
  localChanges: null,
156319
157108
  localFiles: {},
157109
+ declaredPaths,
156320
157110
  identityLedger: {}
156321
157111
  };
156322
157112
  }
156323
- const { status } = candidate;
156324
157113
  const downloads = (status.binaryChanges ?? []).filter(
156325
157114
  (binary) => binary.action === "download"
156326
157115
  );
@@ -156371,6 +157160,7 @@ async function readExportSource(workspace, identityLedger, onPhase) {
156371
157160
  records: records2,
156372
157161
  localChanges: status.changes.length + status.authoredValueSeeds.size,
156373
157162
  localFiles,
157163
+ declaredPaths,
156374
157164
  identityLedger: ledger
156375
157165
  };
156376
157166
  }
@@ -156403,7 +157193,8 @@ async function exportUnityProject(args) {
156403
157193
  const files = exportFiles(
156404
157194
  workspace,
156405
157195
  Object.keys(unityExport.files),
156406
- source.localFiles
157196
+ source.localFiles,
157197
+ source.declaredPaths
156407
157198
  );
156408
157199
  const recordHashes = {};
156409
157200
  for (const [key, record6] of Object.entries(records2)) {
@@ -156540,7 +157331,7 @@ function sortedRecords(records2) {
156540
157331
  function compareOrdinal(left, right) {
156541
157332
  return left < right ? -1 : left > right ? 1 : 0;
156542
157333
  }
156543
- function exportFiles(workspace, fileIds, localFiles) {
157334
+ function exportFiles(workspace, fileIds, localFiles, declaredPaths) {
156544
157335
  const resolvePath = createWorkspaceSourcePathResolver(workspace.root);
156545
157336
  const files = {};
156546
157337
  const failures = [];
@@ -156555,20 +157346,21 @@ function exportFiles(workspace, fileIds, localFiles) {
156555
157346
  failures.push(`${fileId}: the pull recorded no bytes for it`);
156556
157347
  continue;
156557
157348
  }
157349
+ const relativePath = declaredPaths.get(fileId) ?? binary.path;
156558
157350
  if (binary.sha256 === null) {
156559
- failures.push(`${binary.path}: the pull recorded no hash for it`);
157351
+ failures.push(`${relativePath}: the pull recorded no hash for it`);
156560
157352
  continue;
156561
157353
  }
156562
- const path = resolvePath(binary.path);
157354
+ const path = resolvePath(relativePath);
156563
157355
  if (!existsSync13(path)) {
156564
- failures.push(`${binary.path}: missing`);
157356
+ failures.push(`${relativePath}: missing`);
156565
157357
  continue;
156566
157358
  }
156567
157359
  if (sha256File(path) !== binary.sha256) {
156568
- failures.push(`${binary.path}: its bytes differ from the pulled file`);
157360
+ failures.push(`${relativePath}: its bytes differ from the pulled file`);
156569
157361
  continue;
156570
157362
  }
156571
- files[fileId] = { path: binary.path, sha256: binary.sha256 };
157363
+ files[fileId] = { path: relativePath, sha256: binary.sha256 };
156572
157364
  }
156573
157365
  if (failures.length > 0) {
156574
157366
  throw new Error(
@@ -159111,15 +159903,20 @@ function rewriteFilesFromState(workspace, records2 = new Map(
159111
159903
  workspace.root,
159112
159904
  result.formAlternates
159113
159905
  );
159906
+ const authoredSources = new Map(
159907
+ sourceSnapshot?.inputs.map((input) => [input.uri, input.text])
159908
+ );
159114
159909
  const files = result.files.map((file) => {
159115
159910
  const preserved = preservedSourceFiles.get(file.path);
159116
159911
  if (preserved !== void 0 && preservedFileMatchesEmission(preserved, file.content)) {
159117
159912
  return { ...file, content: preserved };
159118
159913
  }
159914
+ const path = join17(workspace.root, file.path);
159915
+ const authored = preserved ?? authoredSources.get(file.path) ?? (existsSync15(path) ? readFileSync19(path, "utf8") : void 0);
159119
159916
  return {
159120
159917
  ...file,
159121
159918
  content: restoreAuthoredMemberForms(
159122
- file.content,
159919
+ authored === void 0 ? file.content : preserveProjectFileRegistrySource(authored, file.content, records2),
159123
159920
  authoredForms.get(file.path) ?? []
159124
159921
  )
159125
159922
  };
@@ -160005,6 +160802,7 @@ var compileNSVoidBody2, compileNSFunction2, compileNSGetter2, compileNSSetter2,
160005
160802
  var init_push = __esm({
160006
160803
  "src/commands/push.ts"() {
160007
160804
  "use strict";
160805
+ init_project_file_source_preservation();
160008
160806
  init_packed_value_parity();
160009
160807
  init_member_kinds();
160010
160808
  init_stored_value_placements();
@@ -160167,7 +160965,7 @@ async function fetchProjectDocument(workspace) {
160167
160965
  deleted: head.deleted
160168
160966
  });
160169
160967
  }
160170
- const sources = [
160968
+ const sources2 = [
160171
160969
  {
160172
160970
  recordKind: ProjectRecordKind.Project,
160173
160971
  entries: readRawSingleton(raw, "project"),
@@ -160299,7 +161097,7 @@ async function fetchProjectDocument(workspace) {
160299
161097
  }
160300
161098
  ];
160301
161099
  const records2 = /* @__PURE__ */ new Map();
160302
- for (const source of sources) {
161100
+ for (const source of sources2) {
160303
161101
  for (const entry of source.entries) {
160304
161102
  if (!source.guard(entry)) {
160305
161103
  throw new Error(
@@ -161423,8 +162221,11 @@ async function finishFormat4Pull(args) {
161423
162221
  const serverByPath = new Map(
161424
162222
  (serverResult?.files ?? []).map((file) => [file.path, file])
161425
162223
  );
161426
- const localRecordFiles = declaringSourcePathsV4(localResult);
161427
- const serverRecordFiles = serverResult === null ? /* @__PURE__ */ new Map() : declaringSourcePathsV4(serverResult);
162224
+ const registryPaths = localResult.analysis.files.registries.map(
162225
+ (registry) => registry.source.uri
162226
+ );
162227
+ const localRecordFiles = declaringSourcePathsV4(localResult, registryPaths);
162228
+ const serverRecordFiles = serverResult === null ? /* @__PURE__ */ new Map() : declaringSourcePathsV4(serverResult, registryPaths);
161428
162229
  const conflictPaths = /* @__PURE__ */ new Set();
161429
162230
  const conflictPathsByKey = /* @__PURE__ */ new Map();
161430
162231
  const authoredLocalConflictKeysByPath = /* @__PURE__ */ new Map();
@@ -161455,6 +162256,7 @@ async function finishFormat4Pull(args) {
161455
162256
  plans,
161456
162257
  localRecordFiles,
161457
162258
  serverRecordFiles,
162259
+ registryPaths,
161458
162260
  previousRecords: workspace.state.records,
161459
162261
  emittedPaths,
161460
162262
  mainLocale: workspaceMainLocale(workspace.state.records)
@@ -161475,7 +162277,14 @@ async function finishFormat4Pull(args) {
161475
162277
  )}: its prior source file ${JSON.stringify(path)} no longer exists.`
161476
162278
  );
161477
162279
  }
161478
- const local = unprojectedConflictKeys.length > 0 ? existing ?? void 0 : localByPath.get(path)?.content;
162280
+ const local = unprojectedConflictKeys.length > 0 ? existing ?? void 0 : (() => {
162281
+ const emitted = localByPath.get(path)?.content;
162282
+ return existing !== null && emitted !== void 0 ? preserveProjectFileRegistrySource(
162283
+ existing,
162284
+ emitted,
162285
+ localRecords
162286
+ ) : emitted;
162287
+ })();
161479
162288
  const server = serverByPath.get(path)?.content;
161480
162289
  const content = conflictPaths.has(path) ? renderConflictFileV4({
161481
162290
  localContent: local,
@@ -161754,7 +162563,7 @@ function cursorsEqual(left, right) {
161754
162563
  (transactionId, index) => transactionId === right.transactionIds[index]
161755
162564
  );
161756
162565
  }
161757
- function declaringSourcePathsV4(emission) {
162566
+ function declaringSourcePathsV4(emission, registryPaths) {
161758
162567
  const paths = /* @__PURE__ */ new Map();
161759
162568
  const add = (key, path) => {
161760
162569
  const existing = paths.get(key);
@@ -161768,6 +162577,12 @@ function declaringSourcePathsV4(emission) {
161768
162577
  for (const key of file.recordKeys) add(key, file.path);
161769
162578
  }
161770
162579
  for (const [key, path] of emission.recordFiles) add(key, path);
162580
+ for (const [key, declaring] of paths) {
162581
+ if (!key.startsWith("project-file:")) continue;
162582
+ for (const path of registryPaths) {
162583
+ if (!declaring.includes(path)) declaring.push(path);
162584
+ }
162585
+ }
161771
162586
  return paths;
161772
162587
  }
161773
162588
  function projectSourcePathsRequiringRewriteV4(args) {
@@ -161790,10 +162605,13 @@ function projectSourcePathsRequiringRewriteV4(args) {
161790
162605
  }
161791
162606
  }
161792
162607
  for (const [key, previous] of Object.entries(args.previousRecords)) {
161793
- if (previous.file === void 0) continue;
161794
- if (!args.emittedPaths.has(previous.file)) continue;
161795
162608
  if (args.localRecordFiles.has(key)) continue;
161796
162609
  if (!args.localStatus.reconstructed.has(key)) continue;
162610
+ if (previous.recordKind === "project-file") {
162611
+ for (const path of args.registryPaths) result.add(path);
162612
+ }
162613
+ if (previous.file === void 0) continue;
162614
+ if (!args.emittedPaths.has(previous.file)) continue;
161797
162615
  result.add(previous.file);
161798
162616
  }
161799
162617
  return result;
@@ -161860,6 +162678,7 @@ function composeProspectivePullRecord(recordKind, recordId, data, serverRecord)
161860
162678
  var init_pull = __esm({
161861
162679
  "src/commands/pull.ts"() {
161862
162680
  "use strict";
162681
+ init_project_file_source_preservation();
161863
162682
  init_push();
161864
162683
  init_source_layout();
161865
162684
  init_project_read_base();
@@ -170786,19 +171605,19 @@ function analyzeManifestScripts(manifest, options = {}) {
170786
171605
  ...options.rootMemberIds ? { rootMemberIds: options.rootMemberIds } : {},
170787
171606
  ...options.sourceUri ? { sourceUri: options.sourceUri } : {}
170788
171607
  });
170789
- const sources = /* @__PURE__ */ new Map();
171608
+ const sources2 = /* @__PURE__ */ new Map();
170790
171609
  const scriptPathByMemberId = /* @__PURE__ */ new Map();
170791
171610
  for (const member of manifest.members) {
170792
171611
  if (member.kind !== "computed" && member.kind !== "scriptFunction" || member.script === null) {
170793
171612
  continue;
170794
171613
  }
170795
171614
  const key = inlineScriptKey(member.script.path, member.id);
170796
- sources.set(key, member.script.sourceText);
171615
+ sources2.set(key, member.script.sourceText);
170797
171616
  scriptPathByMemberId.set(member.id, member.script.path);
170798
171617
  }
170799
171618
  const documents = /* @__PURE__ */ new Map();
170800
171619
  for (const [path, context] of projection.scripts) {
170801
- const source = sources.get(path);
171620
+ const source = sources2.get(path);
170802
171621
  if (source === void 0) continue;
170803
171622
  const uri = manifestScriptUri(path, options.scriptUri);
170804
171623
  documents.set(path, {
@@ -172949,6 +173768,7 @@ function assertMatcher(condition, expectationValue, messageKind, expected, recei
172949
173768
  }
172950
173769
  }
172951
173770
  function errorMessage2(error) {
173771
+ if (error instanceof NSGetterRuntimeError) return error.diagnosticMessage;
172952
173772
  return error instanceof Error ? error.message : String(error);
172953
173773
  }
172954
173774
  function findDelegateTargetMemberId(value) {
@@ -173188,11 +174008,19 @@ function compileSpec(workspace, compile, absolutePath, projectCompilationHash2)
173188
174008
  }
173189
174009
  } catch {
173190
174010
  }
173191
- const compiled = {
173192
- path,
173193
- source,
173194
- action: compile(`file://${absolutePath}`, source)
173195
- };
174011
+ let action;
174012
+ try {
174013
+ action = compile(`file://${absolutePath}`, source);
174014
+ } catch (error) {
174015
+ if (error instanceof CompileError)
174016
+ ERROR_SOURCE_POSITIONS.set(error, {
174017
+ file: path,
174018
+ line: error.line,
174019
+ column: error.column
174020
+ });
174021
+ throw error;
174022
+ }
174023
+ const compiled = { path, source, action };
173196
174024
  atomicWrite(
173197
174025
  artifactPath,
173198
174026
  `${JSON.stringify({
@@ -173222,6 +174050,7 @@ function selectedSpecPaths(workspace, selectors) {
173222
174050
  const normalizedSelectors = selectors.map(
173223
174051
  (selector) => selector.replaceAll("\\", "/").replace(/^\.\//u, "")
173224
174052
  );
174053
+ const realRoot = realpathSync2(workspace.root);
173225
174054
  for (const selector of normalizedSelectors) {
173226
174055
  if (/[*?]/u.test(selector)) continue;
173227
174056
  if (isAbsolute3(selector)) {
@@ -173238,7 +174067,7 @@ function selectedSpecPaths(workspace, selectors) {
173238
174067
  }
173239
174068
  if (!existsSync19(absolute)) continue;
173240
174069
  const real = realpathSync2(absolute);
173241
- const realRelative = relative9(workspace.root, real);
174070
+ const realRelative = relative9(realRoot, real);
173242
174071
  if (realRelative === ".." || realRelative.startsWith(`..${sep9}`)) {
173243
174072
  throw new Error(
173244
174073
  `Spec selector ${JSON.stringify(selector)} resolves outside the workspace through a symlink.`
@@ -173324,7 +174153,7 @@ function callSitePosition(callSiteId) {
173324
174153
  }
173325
174154
  function portableDelegate(value) {
173326
174155
  try {
173327
- return structuredClone(value);
174156
+ return cloneWithRuntimeSources(value);
173328
174157
  } catch (error) {
173329
174158
  throw new NeoTestRegistrationError(
173330
174159
  `Test callback could not be captured deterministically: ${errorMessage2(error)}.`
@@ -173851,8 +174680,15 @@ function runCallback(environment, delegate, deadlineMs) {
173851
174680
  }
173852
174681
  }
173853
174682
  function failureFor(error, file, position, member = null) {
174683
+ let runtimePosition;
174684
+ if (error instanceof NSGetterRuntimeError) {
174685
+ const caller = error.callerNode === void 0 ? void 0 : runtimeSource(error.callerNode);
174686
+ runtimePosition = [error.source, caller].find(
174687
+ (source) => source?.uri?.endsWith(`/${file}`)
174688
+ );
174689
+ }
173854
174690
  const timeout = error instanceof NeoScriptWallClockTimeoutError;
173855
- const sourcePosition = error instanceof NativeFunctionDelegateUnavailableError && error.callSiteId !== void 0 ? callSitePosition(error.callSiteId) : error instanceof Error ? ERROR_SOURCE_POSITIONS.get(error) : void 0;
174691
+ const sourcePosition = error instanceof NativeFunctionDelegateUnavailableError && error.callSiteId !== void 0 ? callSitePosition(error.callSiteId) : error instanceof Error ? ERROR_SOURCE_POSITIONS.get(error) ?? runtimePosition : void 0;
173856
174692
  return {
173857
174693
  kind: error instanceof NeoTestAssertionError ? "assertion" : timeout ? "timeout" : "runtime",
173858
174694
  message: error instanceof NativeFunctionDelegateUnavailableError ? `${error.message} Configure mock.native(...) or mock.function(...) for this exact member before calling production code.` : errorMessage2(error),
@@ -173884,6 +174720,21 @@ async function executeTestFile(spec, document, selected, timeoutMs, interrupted
173884
174720
  );
173885
174721
  }
173886
174722
  async function executeRegisteredSpec(registered, document, selected, timeoutMs, interrupted) {
174723
+ let restoredRuntimeSources = false;
174724
+ const reportFailure = (error, position, member = null) => {
174725
+ if (error instanceof NSGetterRuntimeError && error.node !== void 0 && (error.source === void 0 || error.callerNode !== void 0 && runtimeSource(error.callerNode) === void 0)) {
174726
+ if (!restoredRuntimeSources) {
174727
+ const compiled = lazySpecCompiler(document)(
174728
+ `file:///${registered.spec.path}`,
174729
+ registered.spec.source
174730
+ );
174731
+ copyRuntimeSources(compiled, registered.spec.action);
174732
+ restoredRuntimeSources = true;
174733
+ }
174734
+ error.source ??= runtimeSource(error.node);
174735
+ }
174736
+ return failureFor(error, registered.spec.path, position, member);
174737
+ };
173887
174738
  const results = [];
173888
174739
  const fileFailures = [];
173889
174740
  let testDurationMs = 0;
@@ -173897,9 +174748,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173897
174748
  try {
173898
174749
  runCallback(fixture, hook, suiteDeadline);
173899
174750
  } catch (error) {
173900
- suiteFailures.push(
173901
- failureFor(error, registered.spec.path, suite, "beforeAll")
173902
- );
174751
+ suiteFailures.push(reportFailure(error, suite, "beforeAll"));
173903
174752
  } finally {
173904
174753
  testDurationMs += performance.now() - hookStarted;
173905
174754
  }
@@ -173919,9 +174768,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173919
174768
  try {
173920
174769
  runCallback(environment, hook, deadline);
173921
174770
  } catch (error) {
173922
- failures.push(
173923
- failureFor(error, registered.spec.path, test, "beforeEach")
173924
- );
174771
+ failures.push(reportFailure(error, test, "beforeEach"));
173925
174772
  setupFailed = true;
173926
174773
  break;
173927
174774
  }
@@ -173933,7 +174780,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173933
174780
  try {
173934
174781
  runCallback(environment, test.body, deadline);
173935
174782
  } catch (error) {
173936
- failures.push(failureFor(error, registered.spec.path, test));
174783
+ failures.push(reportFailure(error, test));
173937
174784
  }
173938
174785
  }
173939
174786
  for (const current of [...test.suites].reverse()) {
@@ -173941,9 +174788,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173941
174788
  try {
173942
174789
  runCallback(environment, hook, Date.now() + timeoutMs);
173943
174790
  } catch (error) {
173944
- failures.push(
173945
- failureFor(error, registered.spec.path, test, "afterEach")
173946
- );
174791
+ failures.push(reportFailure(error, test, "afterEach"));
173947
174792
  }
173948
174793
  }
173949
174794
  }
@@ -173976,9 +174821,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173976
174821
  try {
173977
174822
  runCallback(fixture, hook, Date.now() + timeoutMs);
173978
174823
  } catch (error) {
173979
- fileFailures.push(
173980
- failureFor(error, registered.spec.path, suite, "afterAll")
173981
- );
174824
+ fileFailures.push(reportFailure(error, suite, "afterAll"));
173982
174825
  } finally {
173983
174826
  testDurationMs += performance.now() - hookStarted;
173984
174827
  }
@@ -174293,13 +175136,14 @@ async function runTest(workspace, options, dependencies = {}) {
174293
175136
  } catch (error) {
174294
175137
  const diagnosticCode = interrupted || error instanceof NeoTestInterruptedError ? "NEO_TEST_INTERRUPTED" : error instanceof NeoTestPreparedCandidateError ? "NEO_TEST_PREPARED_CANDIDATE" : error instanceof NeoTestUsageError ? "NEO_TEST_USAGE" : error instanceof NeoTestNoTestsError ? "NEO_TEST_NO_TESTS" : error instanceof NeoTestRegistrationError ? "NEO_TEST_REGISTRATION" : phase === "prepare" ? "NEO_TEST_PREPARATION" : phase === "compile" ? "NEO_TEST_COMPILE" : "NEO_TEST_RUNNER";
174295
175138
  exitCode = interrupted || error instanceof NeoTestInterruptedError ? 130 : error instanceof NeoTestUsageError || error instanceof NeoTestReportWriteError || error instanceof NeoTestPreparedCandidateError || diagnosticCode === "NEO_TEST_RUNNER" ? 2 : 1;
175139
+ const errorPosition = error instanceof Error ? ERROR_SOURCE_POSITIONS.get(error) : void 0;
174296
175140
  diagnostics.push({
174297
175141
  severity: "error",
174298
175142
  code: diagnosticCode,
174299
175143
  message: interrupted ? "Neo test was interrupted by SIGINT; partial results were written." : errorMessage2(error),
174300
- file: null,
174301
- line: null,
174302
- column: null,
175144
+ file: errorPosition?.file ?? null,
175145
+ line: errorPosition?.line ?? null,
175146
+ column: errorPosition?.column ?? null,
174303
175147
  related: []
174304
175148
  });
174305
175149
  }
@@ -174512,6 +175356,8 @@ var TEST_BUILD_CACHE_LIMIT_BYTES, ABANDONED_TEMP_MAX_AGE_MS, TEST_CANDIDATE_CACH
174512
175356
  var init_test = __esm({
174513
175357
  "src/commands/test.ts"() {
174514
175358
  "use strict";
175359
+ init_strict_compile_error();
175360
+ init_runtime_source();
174515
175361
  init_test_worker_pool();
174516
175362
  init_project();
174517
175363
  init_test_prelude();
@@ -178078,7 +178924,7 @@ async function commitLocalizedTextLocaleValueEdits(ctx, edits, options) {
178078
178924
  ))
178079
178925
  primaryTextIds.add(textId);
178080
178926
  }
178081
- let sources;
178927
+ let sources2;
178082
178928
  const primaryTargets = /* @__PURE__ */ new Map();
178083
178929
  const changes = [];
178084
178930
  for (const [textId, edit] of composed) {
@@ -178087,8 +178933,8 @@ async function commitLocalizedTextLocaleValueEdits(ctx, edits, options) {
178087
178933
  statusWrite: edit.statusWrite
178088
178934
  });
178089
178935
  if (primaryTextIds.has(textId)) {
178090
- sources ??= indexPrimaryAssignmentSources(document, primaryTextIds);
178091
- const link = edit.assignmentLink === void 0 ? requireSinglePrimaryAssignment(edit.original, sources) : primaryTextAssignmentLink(edit.original, edit.assignmentLink);
178936
+ sources2 ??= indexPrimaryAssignmentSources(document, primaryTextIds);
178937
+ const link = edit.assignmentLink === void 0 ? requireSinglePrimaryAssignment(edit.original, sources2) : primaryTextAssignmentLink(edit.original, edit.assignmentLink);
178092
178938
  primaryTargets.set(textId, localizedTextReferenceTarget(link));
178093
178939
  details = { assignmentLink: link };
178094
178940
  }
@@ -178238,10 +179084,10 @@ function getChangedLocalizedText(changedRecords, textId) {
178238
179084
  );
178239
179085
  }
178240
179086
  function indexPrimaryAssignmentSources(document, textIds) {
178241
- const sources = /* @__PURE__ */ new Map();
179087
+ const sources2 = /* @__PURE__ */ new Map();
178242
179088
  const add = (textId, source) => {
178243
- const list = sources.get(textId);
178244
- if (list === void 0) sources.set(textId, [source]);
179089
+ const list = sources2.get(textId);
179090
+ if (list === void 0) sources2.set(textId, [source]);
178245
179091
  else list.push(source);
178246
179092
  };
178247
179093
  for (const member of document.members) {
@@ -178271,7 +179117,7 @@ function indexPrimaryAssignmentSources(document, textIds) {
178271
179117
  });
178272
179118
  }
178273
179119
  }
178274
- if (candidates.length === 0) return sources;
179120
+ if (candidates.length === 0) return sources2;
178275
179121
  const owners = resolveLocalizableStringWriteOwners(document, candidates);
178276
179122
  for (const [valueId, textId] of candidateTextIds) {
178277
179123
  const member = owners.get(valueId);
@@ -178283,11 +179129,11 @@ function indexPrimaryAssignmentSources(document, textIds) {
178283
179129
  label: `${member.name} value (${valueId})`
178284
179130
  });
178285
179131
  }
178286
- return sources;
179132
+ return sources2;
178287
179133
  }
178288
- function requireSinglePrimaryAssignment(text, sources) {
179134
+ function requireSinglePrimaryAssignment(text, sources2) {
178289
179135
  if (text.archivedAt != null) return primaryTextAssignmentLink(text);
178290
- const found = sources.get(text.id) ?? [];
179136
+ const found = sources2.get(text.id) ?? [];
178291
179137
  if (found.length > 1)
178292
179138
  throw new Error(
178293
179139
  `Choose the source assignment for primary text "${text.id}": ${found.map((source) => source.label).join(", ")}.`
@@ -180890,15 +181736,19 @@ __export(resolve_exports, {
180890
181736
  import { readFileSync as readFileSync28, rmSync as rmSync11, writeFileSync as writeFileSync17 } from "node:fs";
180891
181737
  import { join as join23, relative as relative12 } from "node:path";
180892
181738
  function runResolve(workspace, side) {
180893
- const marked = readMarkedProjectSourcesV4(workspace.root);
181739
+ const sources2 = readResolvedProjectSourcesV4(workspace.root, side);
181740
+ const marked = sources2.filter((file) => file.marked);
180894
181741
  assertRecordConflictsResolveTogetherV4(
180895
181742
  workspace,
180896
181743
  new Set(marked.map((file) => file.path))
180897
181744
  );
181745
+ const declaredPaths = Object.values(workspace.state.records).some(
181746
+ (state) => state.projectBinary?.conflict !== void 0
181747
+ ) ? declaredProjectFilePaths(sources2) : /* @__PURE__ */ new Map();
180898
181748
  const resolvedRecords = adoptServerConflictBases(workspace);
180899
181749
  let resolvedFiles = 0;
180900
181750
  for (const file of marked) {
180901
- writeFileSync17(file.absolutePath, resolveMarkers(file.source, side), "utf8");
181751
+ writeFileSync17(file.absolutePath, file.text, "utf8");
180902
181752
  resolvedFiles += 1;
180903
181753
  }
180904
181754
  let resolvedBinaries = 0;
@@ -180906,6 +181756,7 @@ function runResolve(workspace, side) {
180906
181756
  const binary = state.projectBinary;
180907
181757
  const conflict2 = binary?.conflict;
180908
181758
  if (binary === void 0 || conflict2 === void 0) continue;
181759
+ binary.path = declaredPaths.get(state.recordId) ?? binary.path;
180909
181760
  const destination = join23(workspace.root, binary.path);
180910
181761
  if (side === "theirs") {
180911
181762
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
@@ -180937,18 +181788,32 @@ function runResolve(workspace, side) {
180937
181788
  `Resolved ${resolvedFiles} source file(s), ${resolvedRecords} record conflict(s), and ${resolvedBinaries} binary file(s) keeping the "${side}" side. Review with "neo diff", then "neo push".`
180938
181789
  );
180939
181790
  }
180940
- function readMarkedProjectSourcesV4(root) {
180941
- const files = [];
180942
- for (const absolutePath of listProjectSourceFilesV4(root)) {
181791
+ function declaredProjectFilePaths(sources2) {
181792
+ const analysis = compileNeoProjectSources(
181793
+ sources2.map(({ path, text }) => ({
181794
+ uri: path,
181795
+ kind: requiredProjectSourceKindV4(path, text),
181796
+ text
181797
+ }))
181798
+ );
181799
+ return new Map(
181800
+ projectFileDeclarationsV4(analysis).map((declaration) => [
181801
+ declaration.recordId,
181802
+ declaration.path
181803
+ ])
181804
+ );
181805
+ }
181806
+ function readResolvedProjectSourcesV4(root, side) {
181807
+ return listProjectSourceFilesV4(root).map((absolutePath) => {
180943
181808
  const source = readFileSync28(absolutePath, "utf8");
180944
- if (detectConflictMarkers(source) === null) continue;
180945
- files.push({
181809
+ const marked = detectConflictMarkers(source) !== null;
181810
+ return {
180946
181811
  path: normalizeWorkspaceSourcePath(relative12(root, absolutePath)),
180947
181812
  absolutePath,
180948
- source
180949
- });
180950
- }
180951
- return files;
181813
+ text: marked ? resolveMarkers(source, side) : source,
181814
+ marked
181815
+ };
181816
+ });
180952
181817
  }
180953
181818
  function assertRecordConflictsResolveTogetherV4(workspace, markedPaths) {
180954
181819
  for (const [key, record6] of Object.entries(workspace.state.records)) {
@@ -181024,10 +181889,12 @@ function resolveMarkers(source, side) {
181024
181889
  var init_resolve = __esm({
181025
181890
  "src/commands/resolve.ts"() {
181026
181891
  "use strict";
181892
+ init_project_source_manifest();
181027
181893
  init_workspace();
181028
181894
  init_workspace_status();
181029
181895
  init_source_diagnostics();
181030
181896
  init_workspace_source_path();
181897
+ init_project_file_source();
181031
181898
  init_project_files();
181032
181899
  }
181033
181900
  });
@@ -181285,7 +182152,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
181285
182152
  async function main() {
181286
182153
  const args = parseArgs(process.argv.slice(2));
181287
182154
  if (args.command === "--version") {
181288
- console.log("0.70.2");
182155
+ console.log("0.70.4");
181289
182156
  return;
181290
182157
  }
181291
182158
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {