@neocompose/cli 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/neo.mjs +875 -176
  3. package/package.json +1 -1
package/dist/neo.mjs CHANGED
@@ -829,6 +829,7 @@ var init_document_contracts = __esm({
829
829
  "isStatic",
830
830
  "isVirtual",
831
831
  "isAbstract",
832
+ "accessModifierKind",
832
833
  "defaultValue",
833
834
  "extendsMemberId",
834
835
  "system",
@@ -1185,9 +1186,29 @@ function normalizeDocumentFields(recordKind, value) {
1185
1186
  }
1186
1187
  if (current === void 0) normalized[field] = null;
1187
1188
  }
1189
+ if (recordKind === "member") {
1190
+ normalizeTypeInfoBindings(normalized.returnTypeInfo);
1191
+ if (Array.isArray(normalized.argumentTypes)) {
1192
+ for (const argument2 of normalized.argumentTypes) {
1193
+ normalizeTypeInfoBindings(argument2);
1194
+ }
1195
+ }
1196
+ }
1188
1197
  omitRedundantDeclarationOrder(recordKind, normalized);
1189
1198
  return normalized;
1190
1199
  }
1200
+ function normalizeTypeInfoBindings(value) {
1201
+ if (!isRecord(value)) return;
1202
+ const bindings = value.typeArguments;
1203
+ if (bindings === null || isRecord(bindings) && Object.keys(bindings).length === 0) {
1204
+ delete value.typeArguments;
1205
+ } else if (isRecord(bindings)) {
1206
+ for (const binding of Object.values(bindings)) {
1207
+ normalizeTypeInfoBindings(binding);
1208
+ }
1209
+ }
1210
+ normalizeTypeInfoBindings(value.entryTypeInfo);
1211
+ }
1191
1212
  function memberDefaultApplies(field, type) {
1192
1213
  if (field === "isVirtual" || field === "isAbstract" || field === "locked" || field === "required") {
1193
1214
  return true;
@@ -1508,6 +1529,11 @@ function memberFromDocument(record3, members, owners, classNames, context) {
1508
1529
  owner: owners.get(record3.recordId) ?? { kind: "loose" },
1509
1530
  name: requiredString(data, "name", record3),
1510
1531
  isStatic: booleanValue(data.isStatic, record3, "isStatic"),
1532
+ accessModifier: accessModifierValue(
1533
+ data.accessModifierKind,
1534
+ record3,
1535
+ "accessModifierKind"
1536
+ ),
1511
1537
  modifier,
1512
1538
  locked: booleanValue(field("locked", false), record3, "locked"),
1513
1539
  required: booleanValue(field("required", false), record3, "required"),
@@ -1750,7 +1776,8 @@ function memberToDocumentFields(member, baseData3) {
1750
1776
  kind,
1751
1777
  locked: member.locked,
1752
1778
  required: member.required,
1753
- isStatic: member.isStatic
1779
+ isStatic: member.isStatic,
1780
+ accessModifierKind: member.accessModifier
1754
1781
  };
1755
1782
  applyModifier(fields, member, baseData3);
1756
1783
  if (member.defaultValue !== null) {
@@ -2350,6 +2377,16 @@ function optionalNumberOrNull(value, record3, path) {
2350
2377
  if (value === void 0 || value === null) return null;
2351
2378
  return requiredNumberValue(value, record3, path);
2352
2379
  }
2380
+ function accessModifierValue(value, record3, path) {
2381
+ if (value !== "public" && value !== "protected" && value !== "private") {
2382
+ throw invalidDocument(
2383
+ record3,
2384
+ path,
2385
+ "must be public, protected, or private"
2386
+ );
2387
+ }
2388
+ return value;
2389
+ }
2353
2390
  function requiredString(value, key, record3) {
2354
2391
  return requiredStringValue(value[key], record3, key);
2355
2392
  }
@@ -2606,6 +2643,11 @@ function interfaceFromDocument(record3, context) {
2606
2643
  );
2607
2644
  const memberId = requiredString(member, "id", record3);
2608
2645
  const memberSource = nestedSource(source, "interfaceMember", memberId);
2646
+ const accessModifier = accessModifierValue(
2647
+ member.accessModifierKind,
2648
+ record3,
2649
+ `members.${name}.accessModifierKind`
2650
+ );
2609
2651
  if (member.kind === "property") {
2610
2652
  return {
2611
2653
  id: memberId,
@@ -2621,6 +2663,7 @@ function interfaceFromDocument(record3, context) {
2621
2663
  record3,
2622
2664
  `members.${name}.settable`
2623
2665
  ),
2666
+ accessModifier,
2624
2667
  source: memberSource
2625
2668
  };
2626
2669
  }
@@ -2644,6 +2687,7 @@ function interfaceFromDocument(record3, context) {
2644
2687
  record3,
2645
2688
  `members.${name}.deferred`
2646
2689
  ),
2690
+ accessModifier,
2647
2691
  source: memberSource
2648
2692
  };
2649
2693
  }
@@ -2663,7 +2707,8 @@ function interfaceToDocument(neoInterface) {
2663
2707
  id: member.id,
2664
2708
  kind: "property",
2665
2709
  typeInfo: manifestTypeToDocument(member.type),
2666
- settable: member.settable
2710
+ settable: member.settable,
2711
+ accessModifierKind: member.accessModifier
2667
2712
  };
2668
2713
  } else {
2669
2714
  members[member.name] = {
@@ -2674,7 +2719,8 @@ function interfaceToDocument(neoInterface) {
2674
2719
  name: argument2.name,
2675
2720
  ...manifestTypeToDocument(argument2.type)
2676
2721
  })),
2677
- deferred: member.deferred
2722
+ deferred: member.deferred,
2723
+ accessModifierKind: member.accessModifier
2678
2724
  };
2679
2725
  }
2680
2726
  }
@@ -3460,6 +3506,9 @@ var init_language_spec = __esm({
3460
3506
  "interface",
3461
3507
  "native",
3462
3508
  "override",
3509
+ "private",
3510
+ "protected",
3511
+ "public",
3463
3512
  "return",
3464
3513
  "sealed",
3465
3514
  "static",
@@ -3759,6 +3808,66 @@ function createProjectIndex(project) {
3759
3808
  indexesByListMemberId
3760
3809
  };
3761
3810
  }
3811
+ function memberAccessFailure(args) {
3812
+ const access = args.symbol.accessModifier;
3813
+ if (access === void 0) return null;
3814
+ if (access === "public") return null;
3815
+ const declaringTypeId = args.symbol.inheritedFrom?.typeId ?? args.owner.id;
3816
+ const declaringTypeName = args.symbol.inheritedFrom?.typeName ?? args.owner.name;
3817
+ const accessing = args.context.declaringType ?? args.context.thisClass;
3818
+ const accessingTypeId = accessing?.kind === "named" ? accessing.typeId : void 0;
3819
+ if (access === "private") {
3820
+ if (accessingTypeId === declaringTypeId) return null;
3821
+ return { kind: "private", declaringTypeName };
3822
+ }
3823
+ if (args.owner.kind === "interface") {
3824
+ if (accessingTypeId !== void 0 && classFamilyImplementsInterface(
3825
+ args.project,
3826
+ accessingTypeId,
3827
+ declaringTypeId
3828
+ )) {
3829
+ return null;
3830
+ }
3831
+ return { kind: "interfaceProtected", interfaceName: declaringTypeName };
3832
+ }
3833
+ if (accessingTypeId !== void 0 && typeExtendsInclusive(args.project, accessingTypeId, declaringTypeId)) {
3834
+ return null;
3835
+ }
3836
+ return { kind: "protected", declaringTypeName };
3837
+ }
3838
+ function typeExtendsInclusive(project, typeId, ancestorTypeId) {
3839
+ const visited = /* @__PURE__ */ new Set();
3840
+ const queue = [typeId];
3841
+ while (queue.length > 0) {
3842
+ const current = queue.shift();
3843
+ if (current === ancestorTypeId) return true;
3844
+ if (visited.has(current)) continue;
3845
+ visited.add(current);
3846
+ const type = project.typeById.get(current);
3847
+ for (const baseTypeId of type?.baseTypeIds ?? []) queue.push(baseTypeId);
3848
+ }
3849
+ return false;
3850
+ }
3851
+ function classFamilyImplementsInterface(project, classTypeId, interfaceTypeId) {
3852
+ const visitedClasses = /* @__PURE__ */ new Set();
3853
+ const classQueue = [classTypeId];
3854
+ while (classQueue.length > 0) {
3855
+ const current = classQueue.shift();
3856
+ if (visitedClasses.has(current)) continue;
3857
+ visitedClasses.add(current);
3858
+ const type = project.typeById.get(current);
3859
+ if (type === void 0) continue;
3860
+ for (const interfaceId of type.interfaceTypeIds ?? []) {
3861
+ if (typeExtendsInclusive(project, interfaceId, interfaceTypeId)) {
3862
+ return true;
3863
+ }
3864
+ }
3865
+ for (const baseTypeId of type.baseTypeIds ?? []) {
3866
+ classQueue.push(baseTypeId);
3867
+ }
3868
+ }
3869
+ return false;
3870
+ }
3762
3871
  function withNullability(type, nullable) {
3763
3872
  return { ...type, nullable };
3764
3873
  }
@@ -4437,11 +4546,23 @@ function completionItemsForResolution(snapshot, resolved, word) {
4437
4546
  return resolved.staticType.members.filter((member) => member.kind === "enumMember").map((member) => symbolCompletion(member, word, snapshot));
4438
4547
  }
4439
4548
  if (resolved.staticType) {
4440
- return resolved.staticType.members.filter((member) => member.static === true).map((member) => symbolCompletion(member, word, snapshot));
4441
- }
4442
- return membersForType(snapshot, resolved.type).map(
4443
- (symbol) => symbolCompletion(symbol, word, snapshot)
4444
- );
4549
+ const staticType = resolved.staticType;
4550
+ return staticType.members.filter(
4551
+ (member) => member.static === true && isMemberCompletionAccessible(snapshot, staticType, member)
4552
+ ).map((member) => symbolCompletion(member, word, snapshot));
4553
+ }
4554
+ const owner = resolved.type.kind === "named" ? snapshot.project.typeById.get(resolved.type.typeId) : void 0;
4555
+ return membersForType(snapshot, resolved.type).filter(
4556
+ (symbol) => owner === void 0 || isMemberCompletionAccessible(snapshot, owner, symbol)
4557
+ ).map((symbol) => symbolCompletion(symbol, word, snapshot));
4558
+ }
4559
+ function isMemberCompletionAccessible(snapshot, owner, symbol) {
4560
+ return memberAccessFailure({
4561
+ project: snapshot.project,
4562
+ context: snapshot.context,
4563
+ owner,
4564
+ symbol
4565
+ }) === null;
4445
4566
  }
4446
4567
  function membersForType(snapshot, type) {
4447
4568
  if (type.kind === "named") {
@@ -9788,6 +9909,7 @@ var init_strict_resolver = __esm({
9788
9909
  pos
9789
9910
  );
9790
9911
  }
9912
+ this.assertMemberAccessible(staticType, member, pos);
9791
9913
  if (member.static !== true) {
9792
9914
  throw new CompileError(
9793
9915
  `Member '${staticType.name}.${name}' requires an instance receiver.`,
@@ -9940,6 +10062,7 @@ var init_strict_resolver = __esm({
9940
10062
  pos
9941
10063
  );
9942
10064
  }
10065
+ this.assertMemberAccessible(type, member, pos);
9943
10066
  if (member.static === true) {
9944
10067
  throw new CompileError(
9945
10068
  `Static member '${type.name}.${name}' must be accessed through the type name, not an instance.`,
@@ -10261,6 +10384,7 @@ var init_strict_resolver = __esm({
10261
10384
  pos
10262
10385
  );
10263
10386
  }
10387
+ this.assertMemberAccessible(staticType, member, pos);
10264
10388
  if (member.static !== true) {
10265
10389
  throw new CompileError(
10266
10390
  `Member '${staticType.name}.${callee.name}' requires an instance receiver.`,
@@ -10297,6 +10421,7 @@ var init_strict_resolver = __esm({
10297
10421
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === callee.name
10298
10422
  );
10299
10423
  if (member) {
10424
+ if (type !== void 0) this.assertMemberAccessible(type, member, pos);
10300
10425
  if (member.static === true) {
10301
10426
  throw new CompileError(
10302
10427
  `Static member '${type?.name}.${callee.name}' must be accessed through the type name, not an instance.`,
@@ -10847,6 +10972,7 @@ var init_strict_resolver = __esm({
10847
10972
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === memberName
10848
10973
  );
10849
10974
  if (!symbol2) return null;
10975
+ this.assertMemberAccessible(staticType, symbol2, expression.pos);
10850
10976
  if (symbol2.static !== true) {
10851
10977
  throw new CompileError(
10852
10978
  `Member '${staticType.name}.${memberName}' requires an instance receiver.`,
@@ -10888,6 +11014,9 @@ var init_strict_resolver = __esm({
10888
11014
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === memberName
10889
11015
  );
10890
11016
  if (!symbol) return null;
11017
+ if (type !== void 0) {
11018
+ this.assertMemberAccessible(type, symbol, expression.pos);
11019
+ }
10891
11020
  if (symbol.static === true) {
10892
11021
  throw new CompileError(
10893
11022
  `Static member '${type?.name}.${memberName}' must be accessed through the type name, not an instance.`,
@@ -11265,6 +11394,36 @@ var init_strict_resolver = __esm({
11265
11394
  describe(type) {
11266
11395
  return formatType(type, this.project);
11267
11396
  }
11397
+ /**
11398
+ * Enforces C#-style member accessibility on a resolved member. Runs after
11399
+ * the member is found so hover/definition still resolve while the strict
11400
+ * compile carries the diagnostic.
11401
+ */
11402
+ assertMemberAccessible(owner, symbol, pos) {
11403
+ const failure = memberAccessFailure({
11404
+ project: this.project,
11405
+ context: this.context,
11406
+ owner,
11407
+ symbol
11408
+ });
11409
+ if (failure === null) return;
11410
+ if (failure.kind === "private") {
11411
+ throw new CompileError(
11412
+ `'${symbol.name}' is private to ${failure.declaringTypeName}.`,
11413
+ pos
11414
+ );
11415
+ }
11416
+ if (failure.kind === "interfaceProtected") {
11417
+ throw new CompileError(
11418
+ `'${symbol.name}' is protected; through an ${failure.interfaceName} reference it is accessible only from classes that implement ${failure.interfaceName} and their derived classes.`,
11419
+ pos
11420
+ );
11421
+ }
11422
+ throw new CompileError(
11423
+ `'${symbol.name}' is protected; accessible only from ${failure.declaringTypeName} and derived classes.`,
11424
+ pos
11425
+ );
11426
+ }
11268
11427
  symbolValue(symbol) {
11269
11428
  if (symbol.kind === "root" || symbol.kind === "parameter" || symbol.kind === "value") {
11270
11429
  return {
@@ -11287,6 +11446,13 @@ var init_strict_resolver = __esm({
11287
11446
  if (this.context.staticMember === true && symbol.static !== true) {
11288
11447
  return null;
11289
11448
  }
11449
+ const accessFailure = memberAccessFailure({
11450
+ project: this.project,
11451
+ context: this.context,
11452
+ owner,
11453
+ symbol
11454
+ });
11455
+ if (accessFailure !== null) return null;
11290
11456
  return { owner, symbol };
11291
11457
  }
11292
11458
  nextCallSite(pos) {
@@ -12335,12 +12501,165 @@ var init_parity_contract = __esm({
12335
12501
  });
12336
12502
 
12337
12503
  // ../packages/neoscript-language/src/project-source-ast.ts
12504
+ function isNeoSourceAccessModifier(value) {
12505
+ return value === "public" || value === "protected" || value === "private";
12506
+ }
12507
+ function effectiveSourceAccessModifier(modifiers, ownerKind) {
12508
+ const explicit = modifiers.find(isNeoSourceAccessModifier);
12509
+ if (explicit !== void 0) return explicit;
12510
+ return ownerKind === "interface" ? "public" : "private";
12511
+ }
12338
12512
  var init_project_source_ast = __esm({
12339
12513
  "../packages/neoscript-language/src/project-source-ast.ts"() {
12340
12514
  "use strict";
12341
12515
  }
12342
12516
  });
12343
12517
 
12518
+ // ../packages/neoscript-language/src/project-source-tokens.ts
12519
+ function rangeContains(range2, position) {
12520
+ return positionCompare(range2.start, position) <= 0 && positionCompare(position, range2.end) <= 0;
12521
+ }
12522
+ function rangeSize(range2) {
12523
+ return (range2.end.line - range2.start.line) * 1e6 + range2.end.character - range2.start.character;
12524
+ }
12525
+ function documentPrefix(text, position) {
12526
+ const lines = text.split("\n");
12527
+ return [
12528
+ ...lines.slice(0, position.line),
12529
+ (lines[position.line] ?? "").slice(0, position.character)
12530
+ ].join("\n");
12531
+ }
12532
+ function projectTokenAt(document, position) {
12533
+ const offset = new SourceText(document.text).offsetAt(position);
12534
+ const tokens = projectTokens(document);
12535
+ const containing = tokens.find(
12536
+ (token) => token.start <= offset && offset < token.end
12537
+ );
12538
+ if (containing?.kind === "identifier") return containing;
12539
+ const touching = tokens.find(
12540
+ (token) => token.kind === "identifier" && token.end === offset
12541
+ );
12542
+ return touching ?? containing;
12543
+ }
12544
+ function projectTokens(document) {
12545
+ const cached = PROJECT_TOKEN_CACHE.get(document);
12546
+ if (cached) return cached;
12547
+ const source = new SourceText(document.text);
12548
+ const tokens = lex(document.text).tokens.flatMap(
12549
+ (token) => {
12550
+ if (token.kind !== "string" || !token.raw.startsWith('"""')) {
12551
+ return [token];
12552
+ }
12553
+ const result = [];
12554
+ const contentEnd = token.raw.endsWith('"""') ? token.end - 3 : token.end;
12555
+ let segmentStart = token.start;
12556
+ let cursor = token.start + 3;
12557
+ while (cursor < contentEnd) {
12558
+ if (document.text[cursor] !== "{" || document.text[cursor + 1] === "{") {
12559
+ cursor++;
12560
+ continue;
12561
+ }
12562
+ const close = tripleInterpolationEnd(
12563
+ document.text,
12564
+ cursor + 1,
12565
+ contentEnd
12566
+ );
12567
+ if (close === null) break;
12568
+ if (segmentStart < cursor + 1) {
12569
+ result.push(
12570
+ projectToken(
12571
+ "string",
12572
+ document.text,
12573
+ source,
12574
+ segmentStart,
12575
+ cursor + 1
12576
+ )
12577
+ );
12578
+ }
12579
+ const expressionStart = cursor + 1;
12580
+ const expression = document.text.slice(expressionStart, close);
12581
+ for (const nested of lex(expression).tokens) {
12582
+ if (nested.kind === "eof") continue;
12583
+ const start = expressionStart + nested.start;
12584
+ const end = expressionStart + nested.end;
12585
+ result.push({
12586
+ ...nested,
12587
+ start,
12588
+ end,
12589
+ raw: document.text.slice(start, end),
12590
+ range: source.range(start, end)
12591
+ });
12592
+ }
12593
+ segmentStart = close;
12594
+ cursor = close + 1;
12595
+ }
12596
+ if (segmentStart < token.end) {
12597
+ result.push(
12598
+ projectToken(
12599
+ "string",
12600
+ document.text,
12601
+ source,
12602
+ segmentStart,
12603
+ token.end
12604
+ )
12605
+ );
12606
+ }
12607
+ return result;
12608
+ }
12609
+ );
12610
+ PROJECT_TOKEN_CACHE.set(document, tokens);
12611
+ return tokens;
12612
+ }
12613
+ function projectToken(kind, text, source, start, end) {
12614
+ const raw = text.slice(start, end);
12615
+ return { kind, text: raw, raw, start, end, range: source.range(start, end) };
12616
+ }
12617
+ function tripleInterpolationEnd(text, start, limit) {
12618
+ let depth = 0;
12619
+ let cursor = start;
12620
+ while (cursor < limit) {
12621
+ const char = text[cursor];
12622
+ const next = text[cursor + 1];
12623
+ if (char === '"') {
12624
+ cursor++;
12625
+ while (cursor < limit) {
12626
+ if (text[cursor] === "\\") cursor += 2;
12627
+ else if (text[cursor] === '"') {
12628
+ cursor++;
12629
+ break;
12630
+ } else cursor++;
12631
+ }
12632
+ continue;
12633
+ }
12634
+ if (char === "/" && next === "/") {
12635
+ while (cursor < limit && text[cursor] !== "\n") cursor++;
12636
+ continue;
12637
+ }
12638
+ if (char === "{") depth++;
12639
+ else if (char === "}") {
12640
+ if (depth === 0) return cursor;
12641
+ depth--;
12642
+ }
12643
+ cursor++;
12644
+ }
12645
+ return null;
12646
+ }
12647
+ function rangesEqual(left, right) {
12648
+ return positionCompare(left.start, right.start) === 0 && positionCompare(left.end, right.end) === 0;
12649
+ }
12650
+ function positionCompare(left, right) {
12651
+ return left.line === right.line ? left.character - right.character : left.line - right.line;
12652
+ }
12653
+ var PROJECT_TOKEN_CACHE;
12654
+ var init_project_source_tokens = __esm({
12655
+ "../packages/neoscript-language/src/project-source-tokens.ts"() {
12656
+ "use strict";
12657
+ init_lexer();
12658
+ init_source_text();
12659
+ PROJECT_TOKEN_CACHE = /* @__PURE__ */ new WeakMap();
12660
+ }
12661
+ });
12662
+
12344
12663
  // ../packages/neoscript-language/src/project-source-parser.ts
12345
12664
  function parseNeoProjectSource(sourceValue, kind) {
12346
12665
  const lexed = lex(sourceValue);
@@ -12657,6 +12976,7 @@ function validateDeclarationSemantics(declarations, diagnostics) {
12657
12976
  message: `Member '${member.name}' repeats the '${modifier}' modifier.`
12658
12977
  });
12659
12978
  }
12979
+ validateMemberAccessModifiers(declaration, member, diagnostics);
12660
12980
  if (member.kind !== "function") continue;
12661
12981
  if (declaration.kind === "interface") {
12662
12982
  if (member.body) {
@@ -12702,6 +13022,61 @@ function validateDeclarationSemantics(declarations, diagnostics) {
12702
13022
  }
12703
13023
  }
12704
13024
  }
13025
+ function validateMemberAccessModifiers(declaration, member, diagnostics) {
13026
+ const accessModifiers = [
13027
+ ...new Set(member.modifiers.filter(isNeoSourceAccessModifier))
13028
+ ];
13029
+ if (accessModifiers.length > 1) {
13030
+ diagnostics.push({
13031
+ range: member.nameRange,
13032
+ severity: "error",
13033
+ source: "neo-project",
13034
+ code: "conflicting-access-modifiers",
13035
+ message: `Member '${member.name}' declares more than one access modifier (${accessModifiers.join(", ")}); a member accepts at most one.`
13036
+ });
13037
+ return;
13038
+ }
13039
+ const accessModifier = accessModifiers[0];
13040
+ if (accessModifier === void 0) return;
13041
+ if (declaration.kind === "interface" && accessModifier === "private") {
13042
+ diagnostics.push({
13043
+ range: member.nameRange,
13044
+ severity: "error",
13045
+ source: "neo-project",
13046
+ code: "private-interface-member",
13047
+ message: `Interface member '${member.name}' cannot be private; private interface members are not yet supported. Use public or protected.`
13048
+ });
13049
+ return;
13050
+ }
13051
+ if (accessModifier !== "private") return;
13052
+ if (member.modifiers.includes("virtual")) {
13053
+ diagnostics.push({
13054
+ range: member.nameRange,
13055
+ severity: "error",
13056
+ source: "neo-project",
13057
+ code: "private-virtual-member",
13058
+ message: `Member '${member.name}' cannot be both private and virtual; a private member is never overridable.`
13059
+ });
13060
+ }
13061
+ if (member.modifiers.includes("abstract")) {
13062
+ diagnostics.push({
13063
+ range: member.nameRange,
13064
+ severity: "error",
13065
+ source: "neo-project",
13066
+ code: "private-abstract-member",
13067
+ message: `Member '${member.name}' cannot be both private and abstract; an abstract member exists to be implemented by descendants.`
13068
+ });
13069
+ }
13070
+ if (member.modifiers.includes("override")) {
13071
+ diagnostics.push({
13072
+ range: member.nameRange,
13073
+ severity: "error",
13074
+ source: "neo-project",
13075
+ code: "private-override-member",
13076
+ message: `Member '${member.name}' cannot be both private and override; an override keeps the accessibility of the member it overrides, and a private member cannot be overridden.`
13077
+ });
13078
+ }
13079
+ }
12705
13080
  function isNameToken(token) {
12706
13081
  return token !== void 0 && (token.kind === "identifier" || token.kind === "type" || token.kind === "keyword");
12707
13082
  }
@@ -12737,12 +13112,16 @@ var init_project_source_parser = __esm({
12737
13112
  "use strict";
12738
13113
  init_lexer();
12739
13114
  init_neoflow_graph_parser();
13115
+ init_project_source_ast();
12740
13116
  init_source_text();
12741
13117
  MODIFIERS = /* @__PURE__ */ new Set([
12742
13118
  "abstract",
12743
13119
  "async",
12744
13120
  "native",
12745
13121
  "override",
13122
+ "private",
13123
+ "protected",
13124
+ "public",
12746
13125
  "sealed",
12747
13126
  "static",
12748
13127
  "virtual"
@@ -12780,6 +13159,14 @@ var init_project_source_parser = __esm({
12780
13159
  const annotations = this.parseAnnotations();
12781
13160
  const modifiers = this.parseModifiers();
12782
13161
  if (this.at("class")) {
13162
+ const accessModifier = modifiers.find(isNeoSourceAccessModifier);
13163
+ if (accessModifier !== void 0) {
13164
+ throw this.failure(
13165
+ "source-class-access-modifier",
13166
+ `Access modifier '${accessModifier}' is valid only on class and interface member declarations, not on a class declaration.`,
13167
+ start
13168
+ );
13169
+ }
12783
13170
  return this.parseClass(start, annotations, modifiers);
12784
13171
  }
12785
13172
  if (this.at("interface")) {
@@ -13380,6 +13767,7 @@ var init_project_schema_contract_generated = __esm({
13380
13767
  "isStatic",
13381
13768
  "isVirtual",
13382
13769
  "isAbstract",
13770
+ "accessModifierKind",
13383
13771
  "defaultValue",
13384
13772
  "extendsMemberId",
13385
13773
  "system",
@@ -17191,6 +17579,10 @@ function sourceMemberSymbol(member, owner, typeIds, source) {
17191
17579
  type,
17192
17580
  static: staticMember,
17193
17581
  abstract: member.modifiers.includes("abstract"),
17582
+ accessModifier: effectiveSourceAccessModifier(
17583
+ member.modifiers,
17584
+ owner.declaration.kind
17585
+ ),
17194
17586
  location: location(owner.uri, member.range),
17195
17587
  selectionLocation: location(owner.uri, member.nameRange)
17196
17588
  };
@@ -17525,6 +17917,7 @@ var init_project_source_script_compiler = __esm({
17525
17917
  "use strict";
17526
17918
  init_compiler();
17527
17919
  init_language_spec();
17920
+ init_project_source_ast();
17528
17921
  init_project();
17529
17922
  init_project_root();
17530
17923
  init_source_text();
@@ -18081,6 +18474,7 @@ function validateMembers(environment, diagnostics) {
18081
18474
  }
18082
18475
  for (const modifier of member.modifiers) {
18083
18476
  if (modifier === "async" && member.kind === "function") continue;
18477
+ if (isNeoSourceAccessModifier(modifier)) continue;
18084
18478
  diagnose(
18085
18479
  diagnostics,
18086
18480
  ref.uri,
@@ -18734,6 +19128,7 @@ var init_project_source_type_checker = __esm({
18734
19128
  "../packages/neoscript-language/src/project-source-type-checker.ts"() {
18735
19129
  "use strict";
18736
19130
  init_language_spec();
19131
+ init_project_source_ast();
18737
19132
  BUILTIN_ARITY = /* @__PURE__ */ new Map([
18738
19133
  ["List", 1],
18739
19134
  ["Set", 1],
@@ -18792,7 +19187,13 @@ function analyzeNeoProjectSources(inputs) {
18792
19187
  const symbols = [];
18793
19188
  for (const [uri, document] of documents) {
18794
19189
  for (const declaration of document.declarations) {
18795
- collectDeclarationSymbols(uri, declaration, symbols, diagnostics);
19190
+ collectDeclarationSymbols(
19191
+ uri,
19192
+ document,
19193
+ declaration,
19194
+ symbols,
19195
+ diagnostics
19196
+ );
18796
19197
  }
18797
19198
  }
18798
19199
  validateSymbolUniqueness(symbols, diagnostics);
@@ -18802,7 +19203,7 @@ function analyzeNeoProjectSources(inputs) {
18802
19203
  diagnostics.push(...compileProjectSourceBodies(documents).diagnostics);
18803
19204
  return { documents, symbols, diagnostics };
18804
19205
  }
18805
- function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
19206
+ function collectDeclarationSymbols(uri, document, declaration, symbols, diagnostics) {
18806
19207
  const declarationKind = declaration.kind;
18807
19208
  collectSymbol(
18808
19209
  uri,
@@ -18812,7 +19213,9 @@ function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18812
19213
  declaration.annotations,
18813
19214
  void 0,
18814
19215
  symbols,
18815
- diagnostics
19216
+ diagnostics,
19217
+ void 0,
19218
+ declaration.kind === "global" ? declaration.type.name : void 0
18816
19219
  );
18817
19220
  if (declaration.kind === "enum") {
18818
19221
  for (const option of declaration.options) {
@@ -18829,7 +19232,10 @@ function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18829
19232
  }
18830
19233
  return;
18831
19234
  }
18832
- if (declaration.kind === "global") return;
19235
+ if (declaration.kind === "global") {
19236
+ collectGlobalChildSymbols(uri, document, declaration, symbols, diagnostics);
19237
+ return;
19238
+ }
18833
19239
  if (declaration.kind === "class") {
18834
19240
  for (const parameter3 of declaration.genericParameters) {
18835
19241
  collectSymbol(
@@ -18886,6 +19292,104 @@ function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18886
19292
  }
18887
19293
  }
18888
19294
  }
19295
+ function collectGlobalChildSymbols(uri, document, declaration, symbols, diagnostics) {
19296
+ if (declaration.type.name === "Root") return;
19297
+ const initializer = declaration.initializer;
19298
+ const tokens = lex(document.sourceText).tokens.filter(
19299
+ (token) => token.kind !== "comment" && token.kind !== "eof" && rangeWithin(initializer.range, token.range)
19300
+ );
19301
+ let braceDepth = 0;
19302
+ let groupDepth = 0;
19303
+ let pendingAnnotations = [];
19304
+ for (let index = 0; index < tokens.length; index++) {
19305
+ const token = tokens[index];
19306
+ if (token.text === "{") {
19307
+ braceDepth++;
19308
+ continue;
19309
+ }
19310
+ if (token.text === "}") {
19311
+ braceDepth--;
19312
+ continue;
19313
+ }
19314
+ if (braceDepth !== 1) continue;
19315
+ if (token.text === "(" || token.text === "[") {
19316
+ groupDepth++;
19317
+ continue;
19318
+ }
19319
+ if (token.text === ")" || token.text === "]") {
19320
+ groupDepth--;
19321
+ continue;
19322
+ }
19323
+ if (groupDepth !== 0) continue;
19324
+ if (token.text === ";") {
19325
+ pendingAnnotations = [];
19326
+ continue;
19327
+ }
19328
+ if (token.text === "@") {
19329
+ const annotation2 = scanAnnotation(tokens, index);
19330
+ if (annotation2) {
19331
+ pendingAnnotations.push(annotation2.annotation);
19332
+ index = annotation2.endIndex;
19333
+ }
19334
+ continue;
19335
+ }
19336
+ const name = tokens[index + 1];
19337
+ if ((token.kind === "type" || token.kind === "identifier") && name?.kind === "identifier" && tokens[index + 2]?.text === "=" && tokens[index + 3]?.text !== "=" && // A pending annotation or statement boundary precedes a declaration;
19338
+ // `key: value` fields and expression member accesses never do.
19339
+ tokens[index - 1]?.text !== "." && tokens[index - 1]?.text !== ":") {
19340
+ collectSymbol(
19341
+ uri,
19342
+ "graphChild",
19343
+ name.text,
19344
+ name.range,
19345
+ pendingAnnotations,
19346
+ declaration.name,
19347
+ symbols,
19348
+ diagnostics,
19349
+ void 0,
19350
+ token.text
19351
+ );
19352
+ pendingAnnotations = [];
19353
+ index += 2;
19354
+ }
19355
+ }
19356
+ }
19357
+ function scanAnnotation(tokens, atIndex) {
19358
+ const marker = tokens[atIndex];
19359
+ const name = tokens[atIndex + 1];
19360
+ if (!name || name.kind !== "identifier" && name.kind !== "keyword") {
19361
+ return null;
19362
+ }
19363
+ const argumentsList2 = [];
19364
+ let endIndex = atIndex + 1;
19365
+ let endRange = name.range;
19366
+ if (tokens[atIndex + 2]?.text === "(") {
19367
+ let depth = 0;
19368
+ for (let index = atIndex + 2; index < tokens.length; index++) {
19369
+ const token = tokens[index];
19370
+ if (token.text === "(") depth++;
19371
+ else if (token.text === ")" && --depth === 0) {
19372
+ endIndex = index;
19373
+ endRange = token.range;
19374
+ break;
19375
+ } else if (depth === 1 && token.kind === "string") {
19376
+ argumentsList2.push({ text: token.raw, range: token.range });
19377
+ }
19378
+ }
19379
+ }
19380
+ return {
19381
+ annotation: {
19382
+ name: name.text,
19383
+ nameRange: name.range,
19384
+ arguments: argumentsList2,
19385
+ range: { start: marker.range.start, end: endRange.end }
19386
+ },
19387
+ endIndex
19388
+ };
19389
+ }
19390
+ function rangeWithin(outer, inner) {
19391
+ return positionCompare(outer.start, inner.start) <= 0 && positionCompare(inner.end, outer.end) <= 0;
19392
+ }
18889
19393
  function collectFlowSymbols(uri, content, ownerName, scopeRange, symbols, diagnostics) {
18890
19394
  for (const binding of content.bindings) {
18891
19395
  collectSymbol(
@@ -19177,6 +19681,8 @@ var init_project_source_analysis = __esm({
19177
19681
  "../packages/neoscript-language/src/project-source-analysis.ts"() {
19178
19682
  "use strict";
19179
19683
  init_language_spec();
19684
+ init_lexer();
19685
+ init_project_source_tokens();
19180
19686
  init_project_source_parser();
19181
19687
  init_project_source_semantics();
19182
19688
  init_project_source_script_compiler();
@@ -21242,151 +21748,6 @@ var init_project_schema_manifest = __esm({
21242
21748
  }
21243
21749
  });
21244
21750
 
21245
- // ../packages/neoscript-language/src/project-source-tokens.ts
21246
- function rangeContains(range2, position) {
21247
- return positionCompare(range2.start, position) <= 0 && positionCompare(position, range2.end) <= 0;
21248
- }
21249
- function rangeSize(range2) {
21250
- return (range2.end.line - range2.start.line) * 1e6 + range2.end.character - range2.start.character;
21251
- }
21252
- function documentPrefix(text, position) {
21253
- const lines = text.split("\n");
21254
- return [
21255
- ...lines.slice(0, position.line),
21256
- (lines[position.line] ?? "").slice(0, position.character)
21257
- ].join("\n");
21258
- }
21259
- function projectTokenAt(document, position) {
21260
- const offset = new SourceText(document.text).offsetAt(position);
21261
- const tokens = projectTokens(document);
21262
- const containing = tokens.find(
21263
- (token) => token.start <= offset && offset < token.end
21264
- );
21265
- if (containing?.kind === "identifier") return containing;
21266
- const touching = tokens.find(
21267
- (token) => token.kind === "identifier" && token.end === offset
21268
- );
21269
- return touching ?? containing;
21270
- }
21271
- function projectTokens(document) {
21272
- const cached = PROJECT_TOKEN_CACHE.get(document);
21273
- if (cached) return cached;
21274
- const source = new SourceText(document.text);
21275
- const tokens = lex(document.text).tokens.flatMap(
21276
- (token) => {
21277
- if (token.kind !== "string" || !token.raw.startsWith('"""')) {
21278
- return [token];
21279
- }
21280
- const result = [];
21281
- const contentEnd = token.raw.endsWith('"""') ? token.end - 3 : token.end;
21282
- let segmentStart = token.start;
21283
- let cursor = token.start + 3;
21284
- while (cursor < contentEnd) {
21285
- if (document.text[cursor] !== "{" || document.text[cursor + 1] === "{") {
21286
- cursor++;
21287
- continue;
21288
- }
21289
- const close = tripleInterpolationEnd(
21290
- document.text,
21291
- cursor + 1,
21292
- contentEnd
21293
- );
21294
- if (close === null) break;
21295
- if (segmentStart < cursor + 1) {
21296
- result.push(
21297
- projectToken(
21298
- "string",
21299
- document.text,
21300
- source,
21301
- segmentStart,
21302
- cursor + 1
21303
- )
21304
- );
21305
- }
21306
- const expressionStart = cursor + 1;
21307
- const expression = document.text.slice(expressionStart, close);
21308
- for (const nested of lex(expression).tokens) {
21309
- if (nested.kind === "eof") continue;
21310
- const start = expressionStart + nested.start;
21311
- const end = expressionStart + nested.end;
21312
- result.push({
21313
- ...nested,
21314
- start,
21315
- end,
21316
- raw: document.text.slice(start, end),
21317
- range: source.range(start, end)
21318
- });
21319
- }
21320
- segmentStart = close;
21321
- cursor = close + 1;
21322
- }
21323
- if (segmentStart < token.end) {
21324
- result.push(
21325
- projectToken(
21326
- "string",
21327
- document.text,
21328
- source,
21329
- segmentStart,
21330
- token.end
21331
- )
21332
- );
21333
- }
21334
- return result;
21335
- }
21336
- );
21337
- PROJECT_TOKEN_CACHE.set(document, tokens);
21338
- return tokens;
21339
- }
21340
- function projectToken(kind, text, source, start, end) {
21341
- const raw = text.slice(start, end);
21342
- return { kind, text: raw, raw, start, end, range: source.range(start, end) };
21343
- }
21344
- function tripleInterpolationEnd(text, start, limit) {
21345
- let depth = 0;
21346
- let cursor = start;
21347
- while (cursor < limit) {
21348
- const char = text[cursor];
21349
- const next = text[cursor + 1];
21350
- if (char === '"') {
21351
- cursor++;
21352
- while (cursor < limit) {
21353
- if (text[cursor] === "\\") cursor += 2;
21354
- else if (text[cursor] === '"') {
21355
- cursor++;
21356
- break;
21357
- } else cursor++;
21358
- }
21359
- continue;
21360
- }
21361
- if (char === "/" && next === "/") {
21362
- while (cursor < limit && text[cursor] !== "\n") cursor++;
21363
- continue;
21364
- }
21365
- if (char === "{") depth++;
21366
- else if (char === "}") {
21367
- if (depth === 0) return cursor;
21368
- depth--;
21369
- }
21370
- cursor++;
21371
- }
21372
- return null;
21373
- }
21374
- function rangesEqual(left, right) {
21375
- return positionCompare(left.start, right.start) === 0 && positionCompare(left.end, right.end) === 0;
21376
- }
21377
- function positionCompare(left, right) {
21378
- return left.line === right.line ? left.character - right.character : left.line - right.line;
21379
- }
21380
- var PROJECT_TOKEN_CACHE;
21381
- var init_project_source_tokens = __esm({
21382
- "../packages/neoscript-language/src/project-source-tokens.ts"() {
21383
- "use strict";
21384
- init_lexer();
21385
- init_source_text();
21386
- PROJECT_TOKEN_CACHE = /* @__PURE__ */ new WeakMap();
21387
- }
21388
- });
21389
-
21390
21751
  // ../packages/neoscript-language/src/quick-fixes.ts
21391
21752
  function quickFixFor(document, diagnostic) {
21392
21753
  if (diagnostic.code === "missing-semicolon") {
@@ -21703,11 +22064,17 @@ function projectMemberCompletions(analysis, document, position) {
21703
22064
  if (!owner || owner.kind !== "identifier") return null;
21704
22065
  const resolvedOwner = projectSymbolAt(analysis, document, owner.range.start);
21705
22066
  if (!resolvedOwner) return null;
22067
+ const ownerNames = /* @__PURE__ */ new Set();
21706
22068
  const typeName = projectSymbolTypeName(resolvedOwner.symbol);
21707
- if (!typeName) return null;
21708
- return analysis.symbols.filter(
21709
- (symbol) => symbol.kind === "member" && symbol.ownerName === typeName
22069
+ if (typeName) ownerNames.add(typeName);
22070
+ if (resolvedOwner.symbol.kind === "global") {
22071
+ ownerNames.add(resolvedOwner.symbol.name);
22072
+ }
22073
+ if (ownerNames.size === 0) return null;
22074
+ const members = analysis.symbols.filter(
22075
+ (symbol) => (symbol.kind === "member" || symbol.kind === "graphChild") && symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
21710
22076
  );
22077
+ return members.length > 0 ? members : null;
21711
22078
  }
21712
22079
  function projectConstructorArgumentCompletions(analysis, document, position) {
21713
22080
  const tokens = lex(document.text).tokens.filter(
@@ -21799,6 +22166,15 @@ function graphConstructorNamedArguments(typeName) {
21799
22166
  { name: "reason", type: "string" },
21800
22167
  { name: "duration", type: "decimal?" }
21801
22168
  ];
22169
+ case "Vector2":
22170
+ case "Vector2Int":
22171
+ case "Vector3":
22172
+ case "Vector3Int":
22173
+ case "Color":
22174
+ return graphConstructorParameters(typeName).map((parameter3) => ({
22175
+ name: parameter3.name,
22176
+ type: parameter3.type
22177
+ }));
21802
22178
  default:
21803
22179
  return null;
21804
22180
  }
@@ -22234,6 +22610,19 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
22234
22610
  return { token, symbol: visibleCandidates[0] };
22235
22611
  }
22236
22612
  if (visibleCandidates.length === 0) return null;
22613
+ const sourceTokens = projectTokens(document);
22614
+ const tokenIndex = sourceTokens.findIndex(
22615
+ (candidate) => candidate.start === token.start && candidate.end === token.end
22616
+ );
22617
+ if (sourceTokens[tokenIndex + 1]?.text === "=" && sourceTokens[tokenIndex + 2]?.text !== "=") {
22618
+ const constructed = constructionMemberSymbol(
22619
+ analysis,
22620
+ document,
22621
+ token,
22622
+ visibleCandidates
22623
+ );
22624
+ if (constructed) return { token, symbol: constructed };
22625
+ }
22237
22626
  const source = analysis.documents.get(document.uri);
22238
22627
  const owner = source ? declarationContaining(source, token.range.start)?.name : void 0;
22239
22628
  if (owner) {
@@ -22242,10 +22631,6 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
22242
22631
  );
22243
22632
  if (owned.length === 1) return { token, symbol: owned[0] };
22244
22633
  }
22245
- const sourceTokens = projectTokens(document);
22246
- const tokenIndex = sourceTokens.findIndex(
22247
- (candidate) => candidate.start === token.start && candidate.end === token.end
22248
- );
22249
22634
  const dotOwner = sourceTokens[tokenIndex - 1]?.text === "." ? sourceTokens[tokenIndex - 2] : void 0;
22250
22635
  if (dotOwner) {
22251
22636
  const resolvedOwner = indexedProjectSymbolAt(
@@ -22254,15 +22639,39 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
22254
22639
  dotOwner.range.start,
22255
22640
  index
22256
22641
  );
22257
- const ownerType = resolvedOwner ? projectSymbolTypeName(resolvedOwner.symbol) : dotOwner.text;
22642
+ const ownerNames = /* @__PURE__ */ new Set();
22643
+ if (resolvedOwner) {
22644
+ const typeName = projectSymbolTypeName(resolvedOwner.symbol);
22645
+ if (typeName) ownerNames.add(typeName);
22646
+ if (resolvedOwner.symbol.kind === "global") {
22647
+ ownerNames.add(resolvedOwner.symbol.name);
22648
+ }
22649
+ } else {
22650
+ ownerNames.add(dotOwner.text);
22651
+ }
22258
22652
  const qualified = visibleCandidates.filter(
22259
- (symbol) => symbol.ownerName === ownerType
22653
+ (symbol) => symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
22260
22654
  );
22261
22655
  if (qualified.length === 1) return { token, symbol: qualified[0] };
22262
22656
  }
22263
22657
  const projectLevel = visibleCandidates.filter((symbol) => !symbol.ownerName);
22264
22658
  return projectLevel.length === 1 ? { token, symbol: projectLevel[0] } : null;
22265
22659
  }
22660
+ function constructionMemberSymbol(analysis, document, token, candidates) {
22661
+ const site = constructionSiteAt(analysis, document, token.range.start);
22662
+ let current = site?.enclosingTypeName ?? null;
22663
+ const visited = /* @__PURE__ */ new Set();
22664
+ while (current !== null && !visited.has(current)) {
22665
+ visited.add(current);
22666
+ const owned = candidates.find(
22667
+ (symbol) => symbol.kind === "member" && symbol.ownerName === current
22668
+ );
22669
+ if (owned) return owned;
22670
+ const declaration = findTypeDeclaration(analysis, current);
22671
+ current = declaration?.kind === "class" ? resolvableBaseClassName(analysis, declaration) : null;
22672
+ }
22673
+ return null;
22674
+ }
22266
22675
  function projectSymbolTypeName(symbol) {
22267
22676
  switch (symbol.kind) {
22268
22677
  case "class":
@@ -22360,24 +22769,222 @@ function signatureResult(name, parameters, activeParameter) {
22360
22769
  };
22361
22770
  }
22362
22771
  function contextualConstructorType(analysis, document, position) {
22772
+ return constructionSiteAt(analysis, document, position)?.expectedTypeName ?? null;
22773
+ }
22774
+ function initializerRootAt(analysis, document, position) {
22363
22775
  const source = analysis.documents.get(document.uri);
22364
22776
  if (!source) return null;
22365
22777
  for (const declaration of source.declarations) {
22366
22778
  if (declaration.kind === "global") {
22367
22779
  if (rangeContains(declaration.initializer.range, position)) {
22368
- return declaration.type.name;
22780
+ return {
22781
+ type: declaration.type,
22782
+ range: declaration.initializer.range
22783
+ };
22369
22784
  }
22370
22785
  continue;
22371
22786
  }
22372
22787
  if (declaration.kind === "enum") continue;
22373
22788
  for (const member of declaration.members) {
22374
22789
  if (member.kind === "field" && member.initializer && rangeContains(member.initializer.range, position)) {
22375
- return member.type.name;
22790
+ return { type: member.type, range: member.initializer.range };
22376
22791
  }
22377
22792
  }
22378
22793
  }
22379
22794
  return null;
22380
22795
  }
22796
+ function constructionSiteAt(analysis, document, position) {
22797
+ const root = initializerRootAt(analysis, document, position);
22798
+ if (!root) return null;
22799
+ const tokens = projectTokens(document).filter(
22800
+ (token) => token.kind !== "comment" && token.kind !== "eof" && rangeContains(root.range, token.range.start) && positionCompare(token.range.start, position) < 0
22801
+ );
22802
+ const stack = [];
22803
+ const expectedType = () => {
22804
+ const frame = stack.at(-1);
22805
+ if (!frame) return root.type;
22806
+ switch (frame.kind) {
22807
+ case "object":
22808
+ case "dictionary":
22809
+ return frame.slotType;
22810
+ case "list":
22811
+ return frame.elementType;
22812
+ case "call":
22813
+ return constructorArgumentType(analysis, frame);
22814
+ case "opaque":
22815
+ return null;
22816
+ }
22817
+ };
22818
+ let sawNew = false;
22819
+ let pendingNewTypeName = null;
22820
+ let closedConstructorTypeName = null;
22821
+ for (let index = 0; index < tokens.length; index++) {
22822
+ const token = tokens[index];
22823
+ if (token.text === "@") {
22824
+ if ((tokens[index + 1]?.kind === "identifier" || tokens[index + 1]?.kind === "keyword") && tokens[index + 2]?.text === "(") {
22825
+ let depth = 0;
22826
+ index += 2;
22827
+ for (; index < tokens.length; index++) {
22828
+ if (tokens[index].text === "(") depth++;
22829
+ else if (tokens[index].text === ")" && --depth === 0) break;
22830
+ }
22831
+ } else if (tokens[index + 1]?.kind === "identifier") {
22832
+ index += 1;
22833
+ }
22834
+ continue;
22835
+ }
22836
+ if (token.text === "new") {
22837
+ sawNew = true;
22838
+ pendingNewTypeName = expectedType()?.name ?? null;
22839
+ closedConstructorTypeName = null;
22840
+ continue;
22841
+ }
22842
+ if (sawNew && (token.kind === "identifier" || token.kind === "type")) {
22843
+ pendingNewTypeName = token.text;
22844
+ continue;
22845
+ }
22846
+ if (sawNew && token.text === "<") {
22847
+ let depth = 0;
22848
+ for (; index < tokens.length; index++) {
22849
+ if (tokens[index].text === "<") depth++;
22850
+ else if (tokens[index].text === ">" && --depth === 0) break;
22851
+ }
22852
+ continue;
22853
+ }
22854
+ if (token.text === "{") {
22855
+ if (sawNew || closedConstructorTypeName !== null) {
22856
+ stack.push({
22857
+ kind: "object",
22858
+ typeName: pendingNewTypeName ?? closedConstructorTypeName,
22859
+ slotType: null
22860
+ });
22861
+ sawNew = false;
22862
+ pendingNewTypeName = null;
22863
+ } else {
22864
+ const expected = expectedType();
22865
+ if (expected?.name === "Dictionary") {
22866
+ stack.push({
22867
+ kind: "dictionary",
22868
+ valueType: expected.typeArguments[1] ?? null,
22869
+ slotType: null
22870
+ });
22871
+ } else {
22872
+ stack.push({ kind: "opaque" });
22873
+ }
22874
+ }
22875
+ closedConstructorTypeName = null;
22876
+ continue;
22877
+ }
22878
+ if (token.text === "(") {
22879
+ if (sawNew) {
22880
+ stack.push({
22881
+ kind: "call",
22882
+ typeName: pendingNewTypeName,
22883
+ argumentIndex: 0,
22884
+ argumentName: null
22885
+ });
22886
+ sawNew = false;
22887
+ pendingNewTypeName = null;
22888
+ } else {
22889
+ stack.push({ kind: "opaque" });
22890
+ }
22891
+ closedConstructorTypeName = null;
22892
+ continue;
22893
+ }
22894
+ if (token.text === "[") {
22895
+ stack.push({
22896
+ kind: "list",
22897
+ elementType: collectionElementType2(expectedType())
22898
+ });
22899
+ closedConstructorTypeName = null;
22900
+ continue;
22901
+ }
22902
+ if (token.text === ")") {
22903
+ const frame2 = stack.pop();
22904
+ closedConstructorTypeName = frame2?.kind === "call" ? frame2.typeName : null;
22905
+ continue;
22906
+ }
22907
+ if (token.text === "}" || token.text === "]") {
22908
+ stack.pop();
22909
+ closedConstructorTypeName = null;
22910
+ continue;
22911
+ }
22912
+ closedConstructorTypeName = null;
22913
+ const frame = stack.at(-1);
22914
+ if (!frame) continue;
22915
+ if (frame.kind === "object") {
22916
+ if (token.text === "=" && tokens[index + 1]?.text !== "=") {
22917
+ const nameToken = tokens[index - 1];
22918
+ frame.slotType = nameToken && (nameToken.kind === "identifier" || nameToken.kind === "type") ? declaredFieldType(analysis, frame.typeName, nameToken.text) : null;
22919
+ } else if (token.text === ",") {
22920
+ frame.slotType = null;
22921
+ }
22922
+ } else if (frame.kind === "dictionary") {
22923
+ if (token.text === ":") frame.slotType = frame.valueType;
22924
+ else if (token.text === ",") frame.slotType = null;
22925
+ } else if (frame.kind === "call") {
22926
+ if (token.text === ",") {
22927
+ frame.argumentIndex++;
22928
+ frame.argumentName = null;
22929
+ } else if (token.text === ":") {
22930
+ const nameToken = tokens[index - 1];
22931
+ frame.argumentName = nameToken?.kind === "identifier" ? nameToken.text : null;
22932
+ }
22933
+ }
22934
+ }
22935
+ const enclosing = [...stack].reverse().find(
22936
+ (frame) => frame.kind === "object"
22937
+ );
22938
+ return {
22939
+ expectedTypeName: expectedType()?.name ?? null,
22940
+ enclosingTypeName: enclosing?.typeName ?? null
22941
+ };
22942
+ }
22943
+ function collectionElementType2(type) {
22944
+ if (!type) return null;
22945
+ if (type.name === "List" || type.name === "Set") {
22946
+ return type.typeArguments[0] ?? null;
22947
+ }
22948
+ return null;
22949
+ }
22950
+ function constructorArgumentType(analysis, frame) {
22951
+ if (!frame.typeName) return null;
22952
+ const declaration = findTypeDeclaration(analysis, frame.typeName);
22953
+ if (!declaration || declaration.kind === "global" || declaration.kind === "enum") {
22954
+ return null;
22955
+ }
22956
+ const fields = declaration.members.filter(
22957
+ (member) => member.kind === "field" && !member.modifiers.includes("static")
22958
+ );
22959
+ if (frame.argumentName !== null) {
22960
+ return fields.find((field) => field.name === frame.argumentName)?.type ?? null;
22961
+ }
22962
+ return fields[frame.argumentIndex]?.type ?? null;
22963
+ }
22964
+ function declaredFieldType(analysis, typeName, fieldName) {
22965
+ let current = typeName;
22966
+ const visited = /* @__PURE__ */ new Set();
22967
+ while (current !== null && !visited.has(current)) {
22968
+ visited.add(current);
22969
+ const declaration = findTypeDeclaration(analysis, current);
22970
+ if (!declaration || declaration.kind === "global" || declaration.kind === "enum") {
22971
+ return null;
22972
+ }
22973
+ const member = declaration.members.find(
22974
+ (candidate) => candidate.kind === "field" && candidate.name === fieldName
22975
+ );
22976
+ if (member) return member.type;
22977
+ current = declaration.kind === "class" ? resolvableBaseClassName(analysis, declaration) : null;
22978
+ }
22979
+ return null;
22980
+ }
22981
+ function resolvableBaseClassName(analysis, declaration) {
22982
+ for (const base of declaration.baseTypes) {
22983
+ const resolved = findTypeDeclaration(analysis, base.name);
22984
+ if (resolved?.kind === "class") return base.name;
22985
+ }
22986
+ return null;
22987
+ }
22381
22988
  function findTypeDeclaration(analysis, name) {
22382
22989
  for (const document of analysis.documents.values()) {
22383
22990
  const declaration = document.declarations.find(
@@ -22448,6 +23055,35 @@ function graphConstructorParameters(typeName) {
22448
23055
  { type: "string", name: "reason" },
22449
23056
  { type: "decimal?", name: "duration" }
22450
23057
  ];
23058
+ case "Vector2":
23059
+ return [
23060
+ { type: "float", name: "x" },
23061
+ { type: "float", name: "y" }
23062
+ ];
23063
+ case "Vector2Int":
23064
+ return [
23065
+ { type: "int", name: "x" },
23066
+ { type: "int", name: "y" }
23067
+ ];
23068
+ case "Vector3":
23069
+ return [
23070
+ { type: "float", name: "x" },
23071
+ { type: "float", name: "y" },
23072
+ { type: "float", name: "z" }
23073
+ ];
23074
+ case "Vector3Int":
23075
+ return [
23076
+ { type: "int", name: "x" },
23077
+ { type: "int", name: "y" },
23078
+ { type: "int", name: "z" }
23079
+ ];
23080
+ case "Color":
23081
+ return [
23082
+ { type: "float", name: "r" },
23083
+ { type: "float", name: "g" },
23084
+ { type: "float", name: "b" },
23085
+ { type: "float", name: "a" }
23086
+ ];
22451
23087
  default:
22452
23088
  return [];
22453
23089
  }
@@ -23910,6 +24546,11 @@ function assertMember2(value, path) {
23910
24546
  assertMemberOwner(member.owner, `${path}.owner`);
23911
24547
  nonEmptyString(member.name, `${path}.name`);
23912
24548
  booleanAt(member.isStatic, `${path}.isStatic`);
24549
+ stringIn(
24550
+ member.accessModifier,
24551
+ /* @__PURE__ */ new Set(["public", "protected", "private"]),
24552
+ `${path}.accessModifier`
24553
+ );
23913
24554
  stringIn(
23914
24555
  member.modifier,
23915
24556
  /* @__PURE__ */ new Set([
@@ -24340,12 +24981,14 @@ function assertInterfaceMember(value, path) {
24340
24981
  "name",
24341
24982
  "type",
24342
24983
  "settable",
24984
+ "accessModifier",
24343
24985
  "source"
24344
24986
  ]);
24345
24987
  nonEmptyString(member.id, `${path}.id`);
24346
24988
  nonEmptyString(member.name, `${path}.name`);
24347
24989
  assertType2(member.type, `${path}.type`);
24348
24990
  booleanAt(member.settable, `${path}.settable`);
24991
+ assertInterfaceMemberAccessModifier(member.accessModifier, path);
24349
24992
  assertSourceIdentity(member.source, `${path}.source`, "interfaceMember");
24350
24993
  return;
24351
24994
  }
@@ -24357,16 +25000,21 @@ function assertInterfaceMember(value, path) {
24357
25000
  "returnType",
24358
25001
  "arguments",
24359
25002
  "deferred",
25003
+ "accessModifier",
24360
25004
  "source"
24361
25005
  ]);
24362
25006
  nonEmptyString(member.id, `${path}.id`);
24363
25007
  nonEmptyString(member.name, `${path}.name`);
24364
25008
  assertFunctionSignature(member, path);
25009
+ assertInterfaceMemberAccessModifier(member.accessModifier, path);
24365
25010
  assertSourceIdentity(member.source, `${path}.source`, "interfaceMember");
24366
25011
  return;
24367
25012
  }
24368
25013
  invalid(`${path}.kind`, "must be property or function.");
24369
25014
  }
25015
+ function assertInterfaceMemberAccessModifier(value, path) {
25016
+ stringIn(value, /* @__PURE__ */ new Set(["public", "protected"]), `${path}.accessModifier`);
25017
+ }
24370
25018
  function assertEnum2(value, path) {
24371
25019
  const neoEnum = objectAt(value, path, [
24372
25020
  "id",
@@ -25112,6 +25760,7 @@ var init_validate = __esm({
25112
25760
  "owner",
25113
25761
  "name",
25114
25762
  "isStatic",
25763
+ "accessModifier",
25115
25764
  "modifier",
25116
25765
  "locked",
25117
25766
  "required",
@@ -26923,6 +27572,10 @@ function memberCommon(context, ownerClass, declaration, id2, owner, base) {
26923
27572
  owner,
26924
27573
  name: declaration.name,
26925
27574
  isStatic: declaration.modifiers.includes("static"),
27575
+ // Non-class placements (collection entries, generic arguments, loose
27576
+ // members) have no receiver type to scope visibility to; they are
27577
+ // structurally public. Class schema entries apply the C# omission default.
27578
+ accessModifier: owner.kind === "classMember" ? effectiveSourceAccessModifier(declaration.modifiers, "class") : "public",
26926
27579
  modifier: memberModifier2(declaration.modifiers, base?.modifier),
26927
27580
  locked: hasAnnotation(declaration.annotations, "locked"),
26928
27581
  required: declaration.kind === "field" ? !declaration.type.nullable : base?.required ?? false,
@@ -27140,6 +27793,10 @@ function lowerInterface(context, declaration) {
27140
27793
  `${declaration.name}.${member.name}`
27141
27794
  );
27142
27795
  const source = sourceIdentity2(member, "interfaceMember", member.name);
27796
+ const accessModifier = effectiveSourceAccessModifier(
27797
+ member.modifiers,
27798
+ "interface"
27799
+ );
27143
27800
  if (member.kind === "function") {
27144
27801
  return {
27145
27802
  id: memberId,
@@ -27153,6 +27810,7 @@ function lowerInterface(context, declaration) {
27153
27810
  selectionSpan: span(parameter3)
27154
27811
  })),
27155
27812
  deferred: member.modifiers.includes("async"),
27813
+ accessModifier,
27156
27814
  source
27157
27815
  };
27158
27816
  }
@@ -27162,6 +27820,7 @@ function lowerInterface(context, declaration) {
27162
27820
  name: member.name,
27163
27821
  type: lowerType(context, member.type),
27164
27822
  settable: /\bset\s*;/.test(member.body ?? ""),
27823
+ accessModifier,
27165
27824
  source
27166
27825
  };
27167
27826
  });
@@ -28660,11 +29319,12 @@ function emitInterface(context, value) {
28660
29319
  (interfaceId) => required(context.interfaces, interfaceId, "interface").name
28661
29320
  );
28662
29321
  const members = value.members.map((member) => {
29322
+ const access = member.accessModifier === "public" ? "" : `${member.accessModifier} `;
28663
29323
  if (member.kind === "property") {
28664
- return `${id(member.id)}${renderType(context, member.type)} ${member.name} { get;${member.settable ? " set;" : ""} }`;
29324
+ return `${id(member.id)}${access}${renderType(context, member.type)} ${member.name} { get;${member.settable ? " set;" : ""} }`;
28665
29325
  }
28666
29326
  const deferred = member.deferred ? "async " : "";
28667
- return `${id(member.id)}${deferred}${renderReturnType(context, member.returnType)} ${member.name}(${member.arguments.map(
29327
+ return `${id(member.id)}${access}${deferred}${renderReturnType(context, member.returnType)} ${member.name}(${member.arguments.map(
28668
29328
  (argument2) => `${renderType(context, argument2.type)} ${argument2.name}`
28669
29329
  ).join(", ")});`;
28670
29330
  });
@@ -28729,9 +29389,10 @@ ${body ? `${signature} ${body}` : abstractContract ? `${signature};` : `native $
28729
29389
  ${prefix}${type} ${member.name}${initializer === null ? ";" : ` = ${initializer};`}`;
28730
29390
  }
28731
29391
  function memberModifier3(member) {
29392
+ const accessPrefix = `${member.accessModifier} `;
28732
29393
  const staticPrefix = member.isStatic ? "static " : "";
28733
29394
  const modifier = member.modifier === "plain" ? "" : member.modifier === "abstractOverride" ? "abstract override " : member.modifier === "sealedOverride" ? "override " : `${member.modifier} `;
28734
- return `${staticPrefix}${modifier}`;
29395
+ return `${accessPrefix}${staticPrefix}${modifier}`;
28735
29396
  }
28736
29397
  function renderExtendsGenericBindings(context, schemaClass2, base) {
28737
29398
  if (base.genericParameters.length === 0) return "";
@@ -32801,6 +33462,11 @@ var init_neoscript = __esm({
32801
33462
  });
32802
33463
 
32803
33464
  // ../src/models/members/member-kinds.ts
33465
+ function isMemberAccessModifierKind(value) {
33466
+ if (value === "public") return true;
33467
+ if (value === "protected") return true;
33468
+ return value === "private";
33469
+ }
32804
33470
  function isValidCallableIdentifier(value) {
32805
33471
  if (typeof value !== "string") return false;
32806
33472
  if (!FunctionMemberNamePattern.test(value)) return false;
@@ -33244,6 +33910,7 @@ function isMemberBase(value) {
33244
33910
  if (typeof v.locked !== "boolean") return false;
33245
33911
  if (typeof v.required !== "boolean") return false;
33246
33912
  if (typeof v.isStatic !== "boolean") return false;
33913
+ if (!isMemberAccessModifierKind(v.accessModifierKind)) return false;
33247
33914
  if (v.isVirtual !== void 0 && typeof v.isVirtual !== "boolean") {
33248
33915
  return false;
33249
33916
  }
@@ -33299,6 +33966,7 @@ function isMemberOverrideBase(value) {
33299
33966
  if (v.locked !== void 0 && typeof v.locked !== "boolean") return false;
33300
33967
  if (v.required !== void 0 && typeof v.required !== "boolean") return false;
33301
33968
  if (v.isStatic !== void 0 && v.isStatic !== false) return false;
33969
+ if (!isMemberAccessModifierKind(v.accessModifierKind)) return false;
33302
33970
  if (v.isVirtual !== void 0 && typeof v.isVirtual !== "boolean") {
33303
33971
  return false;
33304
33972
  }
@@ -33806,6 +34474,9 @@ function substituteMember(member, env, members) {
33806
34474
  ...binding,
33807
34475
  name: resolved.name,
33808
34476
  locked: resolved.locked,
34477
+ // Accessibility is a slot-owned declaration field — a binding member's
34478
+ // modifier must not change the declaring slot's visibility.
34479
+ accessModifierKind: resolved.accessModifierKind,
33809
34480
  system: resolved.system ?? void 0
33810
34481
  };
33811
34482
  const slotId = getOptionalString(member, "id");
@@ -35112,6 +35783,7 @@ function isNeoInterfaceMember(value) {
35112
35783
  if (value === null) return false;
35113
35784
  const member = value;
35114
35785
  if (typeof member.id !== "string" || member.id.length === 0) return false;
35786
+ if (!isMemberAccessModifierKind(member.accessModifierKind)) return false;
35115
35787
  if (member.kind === "property") {
35116
35788
  if (!isNSTypeInfo(member.typeInfo)) return false;
35117
35789
  if (containsForbiddenMemberTypeInfo(member.typeInfo)) return false;
@@ -44476,6 +45148,29 @@ function migrateMemberStaticOwnership(data) {
44476
45148
  if (Object.prototype.hasOwnProperty.call(data, "isStatic")) return data;
44477
45149
  return { ...data, isStatic: false };
44478
45150
  }
45151
+ function migrateMemberAccessModifier(data) {
45152
+ if (!isRecord5(data)) return data;
45153
+ if (Object.prototype.hasOwnProperty.call(data, "accessModifierKind")) {
45154
+ return data;
45155
+ }
45156
+ return { ...data, accessModifierKind: "public" };
45157
+ }
45158
+ function migrateInterfaceMemberAccessModifiers(data) {
45159
+ if (!isRecord5(data)) return data;
45160
+ const members = data.members;
45161
+ if (!isRecord5(members)) return data;
45162
+ let nextMembers = members;
45163
+ for (const [memberKey, member] of Object.entries(members)) {
45164
+ if (!isRecord5(member)) continue;
45165
+ if (Object.prototype.hasOwnProperty.call(member, "accessModifierKind")) {
45166
+ continue;
45167
+ }
45168
+ if (nextMembers === members) nextMembers = { ...members };
45169
+ nextMembers[memberKey] = { ...member, accessModifierKind: "public" };
45170
+ }
45171
+ if (nextMembers === members) return data;
45172
+ return { ...data, members: nextMembers };
45173
+ }
44479
45174
  function migrateProjectRecordData(args) {
44480
45175
  const currentVersion = CURRENT_PROJECT_RECORD_DATA_SCHEMA_VERSION[args.recordKind];
44481
45176
  if (args.dataSchemaVersion > currentVersion) {
@@ -44659,12 +45354,12 @@ var init_project_record_migrations = __esm({
44659
45354
  init_neoscript_call_receiver_migration();
44660
45355
  CURRENT_PROJECT_RECORD_DATA_SCHEMA_VERSION = {
44661
45356
  [ProjectRecordKind.Project]: 1,
44662
- [ProjectRecordKind.Member]: 4,
45357
+ [ProjectRecordKind.Member]: 5,
44663
45358
  [ProjectRecordKind.Value]: 1,
44664
45359
  [ProjectRecordKind.Class]: 1,
44665
45360
  [ProjectRecordKind.InternalRecordRelation]: 1,
44666
45361
  [ProjectRecordKind.Enum]: 4,
44667
- [ProjectRecordKind.Interface]: 2,
45362
+ [ProjectRecordKind.Interface]: 3,
44668
45363
  [ProjectRecordKind.Dialogue]: 5,
44669
45364
  [ProjectRecordKind.DialogueNode]: 4,
44670
45365
  [ProjectRecordKind.DialogueGroup]: 4,
@@ -44682,7 +45377,8 @@ var init_project_record_migrations = __esm({
44682
45377
  [ProjectRecordKind.Member]: {
44683
45378
  1: migrateGeneralFunctionCallIR,
44684
45379
  2: migrateMemberStaticOwnership,
44685
- 3: migrateExplicitCallReceiverIR
45380
+ 3: migrateExplicitCallReceiverIR,
45381
+ 4: migrateMemberAccessModifier
44686
45382
  },
44687
45383
  [ProjectRecordKind.Value]: {},
44688
45384
  [ProjectRecordKind.Class]: {},
@@ -44690,7 +45386,9 @@ var init_project_record_migrations = __esm({
44690
45386
  [ProjectRecordKind.Enum]: {
44691
45387
  1: migrateEnumOptionKeyOrder
44692
45388
  },
44693
- [ProjectRecordKind.Interface]: {},
45389
+ [ProjectRecordKind.Interface]: {
45390
+ 2: migrateInterfaceMemberAccessModifiers
45391
+ },
44694
45392
  [ProjectRecordKind.Dialogue]: {
44695
45393
  1: migrateDialoguePrimaryLinkedValues,
44696
45394
  2: migrateGeneralFunctionCallIR,
@@ -53057,7 +53755,8 @@ function constructClassValue(info, scope, ctx) {
53057
53755
  classId,
53058
53756
  locked: false,
53059
53757
  required: true,
53060
- isStatic: false
53758
+ isStatic: false,
53759
+ accessModifierKind: "public"
53061
53760
  };
53062
53761
  const createdValues = [];
53063
53762
  const storageKeyDeclarations = /* @__PURE__ */ new Map();