@neocompose/cli 0.31.4 → 0.31.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.6] - 2026-08-15
4
+
5
+ ### Added
6
+
7
+ - Improve shared NeoScript editor intelligence with inferred-type and
8
+ low-noise parameter inlay hints, spelling-aware diagnostics and quick fixes,
9
+ and a contextual `var`-to-explicit-type refactoring.
10
+
11
+ ### Fixed
12
+
13
+ - Disambiguate same-named types, values, members, and contextual enum options
14
+ by their expression role, receiver, and expected type. Member completion and
15
+ navigation now separate static and instance access and follow inherited
16
+ members with derived-first hiding.
17
+
18
+ ## [0.31.5] - 2026-08-15
19
+
20
+ ### Fixed
21
+
22
+ - Resolve callable members through generic class and interface constraints, so
23
+ an open generic value can call guaranteed APIs such as `Class.Clone()`.
24
+
3
25
  ## [0.31.4] - 2026-08-14
4
26
 
5
27
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -1832,6 +1832,21 @@ var init_generated_csharp_identifiers = __esm({
1832
1832
  });
1833
1833
 
1834
1834
  // ../packages/neoscript-language/src/project.ts
1835
+ function neoScriptMemberLookupType(type) {
1836
+ if (type.kind !== "typeParameter") return type;
1837
+ const visited = /* @__PURE__ */ new Set();
1838
+ let current = type;
1839
+ let nullable = type.nullable === true;
1840
+ while (current.kind === "typeParameter") {
1841
+ if (visited.has(current.id) || current.constraint === void 0) {
1842
+ return type;
1843
+ }
1844
+ visited.add(current.id);
1845
+ nullable ||= current.constraint.nullable === true;
1846
+ current = current.constraint;
1847
+ }
1848
+ return nullable && current.nullable !== true ? { ...current, nullable: true } : current;
1849
+ }
1835
1850
  function neoScriptParameterIsOmittable(parameter4) {
1836
1851
  return parameter4.defaultValue !== void 0;
1837
1852
  }
@@ -2469,14 +2484,19 @@ function signatureHelp(snapshot, position) {
2469
2484
  function inlayHints(snapshot, range2) {
2470
2485
  const hints = [];
2471
2486
  for (const call of snapshot.parsed.calls) {
2472
- if (call.kind !== "constructor") continue;
2473
- const type = snapshot.project.typeByName.get(call.name);
2474
- const parameterNames = constructorParameterNames(type);
2487
+ const reference2 = snapshot.parsed.references.find(
2488
+ (candidate) => candidate.range.start.line === call.nameRange.start.line && candidate.range.start.character === call.nameRange.start.character
2489
+ );
2490
+ const resolution = reference2 ? resolveReference(snapshot, reference2) : null;
2491
+ const parameterNames = call.kind === "constructor" ? constructorParameterNames(resolution?.staticType) : resolution?.symbol?.parameters?.map((parameter4) => parameter4.name);
2475
2492
  if (!parameterNames) continue;
2476
2493
  for (let index = 0; index < call.argumentRanges.length; index++) {
2477
2494
  const argumentRange = call.argumentRanges[index];
2478
2495
  const parameterName = parameterNames[index];
2479
2496
  if (!argumentRange || !parameterName) continue;
2497
+ if (obviousParameterName(snapshot, argumentRange, parameterName)) {
2498
+ continue;
2499
+ }
2480
2500
  const position = firstNonWhitespacePosition(snapshot, argumentRange);
2481
2501
  if (range2 && !positionInRange(position, range2)) continue;
2482
2502
  hints.push({
@@ -2487,8 +2507,59 @@ function inlayHints(snapshot, range2) {
2487
2507
  });
2488
2508
  }
2489
2509
  }
2510
+ for (const local of snapshot.parsed.locals) {
2511
+ if (!local.inferred || !local.contextualKeywordRange) continue;
2512
+ const type = resolveDeclaredType(local, snapshot);
2513
+ if (type.kind === "primitive" && type.name === "unknown") continue;
2514
+ const position = local.nameRange.end;
2515
+ if (range2 && !positionInRange(position, range2)) continue;
2516
+ hints.push({
2517
+ position,
2518
+ label: `: ${formatType(type, snapshot.project)}`,
2519
+ kind: "type",
2520
+ paddingLeft: false
2521
+ });
2522
+ }
2490
2523
  return hints;
2491
2524
  }
2525
+ function obviousParameterName(snapshot, argumentRange, parameterName) {
2526
+ const start = snapshot.source.offsetAt(argumentRange.start);
2527
+ const end = snapshot.source.offsetAt(argumentRange.end);
2528
+ const argument2 = snapshot.source.text.slice(start, end).trim();
2529
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(argument2) && normalizeHintName(argument2) === normalizeHintName(parameterName);
2530
+ }
2531
+ function normalizeHintName(name) {
2532
+ return name.replaceAll("_", "").toLocaleLowerCase();
2533
+ }
2534
+ function inferredTypeRefactorings(snapshot, range2) {
2535
+ return snapshot.parsed.locals.flatMap((local) => {
2536
+ if (!local.inferred || !local.contextualKeywordRange || !rangesIntersect(local.declarationRange, range2)) {
2537
+ return [];
2538
+ }
2539
+ const type = resolveDeclaredType(local, snapshot);
2540
+ if (type.kind === "primitive" && type.name === "unknown") return [];
2541
+ const formatted = formatType(type, snapshot.project);
2542
+ return [
2543
+ {
2544
+ title: `Use explicit type '${formatted}'`,
2545
+ kind: "refactor.rewrite",
2546
+ edit: {
2547
+ changes: {
2548
+ [snapshot.uri]: [
2549
+ { range: local.contextualKeywordRange, newText: formatted }
2550
+ ]
2551
+ }
2552
+ }
2553
+ }
2554
+ ];
2555
+ });
2556
+ }
2557
+ function rangesIntersect(left, right) {
2558
+ return positionCompare(left.start, right.end) <= 0 && positionCompare(right.start, left.end) <= 0;
2559
+ }
2560
+ function positionCompare(left, right) {
2561
+ return left.line - right.line || left.character - right.character;
2562
+ }
2492
2563
  function constructorParameterNames(type) {
2493
2564
  const declared = type?.declaredConstructors ?? [];
2494
2565
  if (declared.length > 0) {
@@ -3160,7 +3231,8 @@ function completionItemsForResolution(snapshot, resolved, word) {
3160
3231
  (member) => member.static === true && isMemberCompletionAccessible(snapshot, staticType, member)
3161
3232
  ).map((member) => symbolCompletion(member, word, snapshot));
3162
3233
  }
3163
- const owner = resolved.type.kind === "named" ? snapshot.project.typeById.get(resolved.type.typeId) : void 0;
3234
+ const memberType2 = neoScriptMemberLookupType(resolved.type);
3235
+ const owner = memberType2.kind === "named" ? snapshot.project.typeById.get(memberType2.typeId) : void 0;
3164
3236
  return membersForType(snapshot, resolved.type).filter(
3165
3237
  (symbol) => owner === void 0 || isMemberCompletionAccessible(snapshot, owner, symbol)
3166
3238
  ).map((symbol) => symbolCompletion(symbol, word, snapshot));
@@ -3174,6 +3246,17 @@ function isMemberCompletionAccessible(snapshot, owner, symbol) {
3174
3246
  }) === null;
3175
3247
  }
3176
3248
  function membersForType(snapshot, type) {
3249
+ const memberType2 = neoScriptMemberLookupType(type);
3250
+ if (memberType2 !== type) {
3251
+ const members = [...membersForType(snapshot, memberType2)];
3252
+ if (type.kind !== "typeParameter" || type.nullable === true) return members;
3253
+ const named = memberType2.kind === "named" ? snapshot.project.typeById.get(memberType2.typeId) : void 0;
3254
+ if (named?.kind !== "class") return members;
3255
+ const cloneId = `builtin:${named.id}:Clone`;
3256
+ return members.map(
3257
+ (member) => member.id === cloneId ? cloneSymbol(named, type) : member
3258
+ );
3259
+ }
3177
3260
  if (type.kind === "named") {
3178
3261
  const named = snapshot.project.typeById.get(type.typeId);
3179
3262
  if (!named) return [];
@@ -3950,13 +4033,13 @@ function stringContainsSymbol() {
3950
4033
  "Returns true when the string contains the substring."
3951
4034
  );
3952
4035
  }
3953
- function cloneSymbol(type) {
4036
+ function cloneSymbol(type, returnType = { kind: "named", typeId: type.id }) {
3954
4037
  return {
3955
4038
  id: `builtin:${type.id}:Clone`,
3956
4039
  name: "Clone",
3957
4040
  kind: "method",
3958
- type: { kind: "named", typeId: type.id },
3959
- returnType: { kind: "named", typeId: type.id },
4041
+ type: returnType,
4042
+ returnType,
3960
4043
  parameters: [],
3961
4044
  documentation: `Deep-copy this ${type.name} into a new parentless writable value.`
3962
4045
  };
@@ -11871,11 +11954,12 @@ var init_strict_resolver = __esm({
11871
11954
  pos
11872
11955
  );
11873
11956
  }
11874
- if (receiver.type.kind === "named") {
11875
- const type = this.project.typeById.get(receiver.type.typeId);
11957
+ const memberReceiverType = neoScriptMemberLookupType(receiver.type);
11958
+ if (memberReceiverType.kind === "named") {
11959
+ const type = this.project.typeById.get(memberReceiverType.typeId);
11876
11960
  if (!type) {
11877
11961
  throw new CompileError(
11878
- `Type ${receiver.type.typeId} was not found in the project.`,
11962
+ `Type ${memberReceiverType.typeId} was not found in the project.`,
11879
11963
  pos
11880
11964
  );
11881
11965
  }
@@ -11909,7 +11993,7 @@ var init_strict_resolver = __esm({
11909
11993
  }
11910
11994
  const memberType2 = substituteTypeParameters(
11911
11995
  member.type,
11912
- receiver.type,
11996
+ memberReceiverType,
11913
11997
  type
11914
11998
  );
11915
11999
  if (member.computed === true) {
@@ -12350,8 +12434,9 @@ var init_strict_resolver = __esm({
12350
12434
  pos
12351
12435
  );
12352
12436
  }
12353
- if (receiver.type.kind === "named") {
12354
- const type = this.project.typeById.get(receiver.type.typeId);
12437
+ const memberReceiverType = neoScriptMemberLookupType(receiver.type);
12438
+ if (memberReceiverType.kind === "named") {
12439
+ const type = this.project.typeById.get(memberReceiverType.typeId);
12355
12440
  const member = type?.members.find(
12356
12441
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === callee.name
12357
12442
  );
@@ -12365,7 +12450,10 @@ var init_strict_resolver = __esm({
12365
12450
  }
12366
12451
  return this.resolveSymbolCall(
12367
12452
  member,
12368
- { kind: "instance", expression: receiver },
12453
+ {
12454
+ kind: "instance",
12455
+ expression: memberReceiverType === receiver.type ? receiver : { ...receiver, type: memberReceiverType }
12456
+ },
12369
12457
  argumentsList2,
12370
12458
  scope,
12371
12459
  pos,
@@ -12385,7 +12473,7 @@ var init_strict_resolver = __esm({
12385
12473
  pos
12386
12474
  );
12387
12475
  }
12388
- const schemaClass2 = toWireType(receiver.type, this.project);
12476
+ const schemaClass2 = toWireType(memberReceiverType, this.project);
12389
12477
  return {
12390
12478
  pointer: {
12391
12479
  type: "function" /* Function */,
@@ -17457,7 +17545,7 @@ var init_project_source_parser = __esm({
17457
17545
 
17458
17546
  // ../packages/neoscript-language/src/project-source-tokens.ts
17459
17547
  function rangeContains(range2, position) {
17460
- return positionCompare(range2.start, position) <= 0 && positionCompare(position, range2.end) <= 0;
17548
+ return positionCompare2(range2.start, position) <= 0 && positionCompare2(position, range2.end) <= 0;
17461
17549
  }
17462
17550
  function rangeSize(range2) {
17463
17551
  return (range2.end.line - range2.start.line) * 1e6 + range2.end.character - range2.start.character;
@@ -17585,9 +17673,9 @@ function tripleInterpolationEnd(text, start, limit) {
17585
17673
  return null;
17586
17674
  }
17587
17675
  function rangesEqual(left, right) {
17588
- return positionCompare(left.start, right.start) === 0 && positionCompare(left.end, right.end) === 0;
17676
+ return positionCompare2(left.start, right.start) === 0 && positionCompare2(left.end, right.end) === 0;
17589
17677
  }
17590
- function positionCompare(left, right) {
17678
+ function positionCompare2(left, right) {
17591
17679
  return left.line === right.line ? left.character - right.character : left.line - right.line;
17592
17680
  }
17593
17681
  var PROJECT_TOKEN_CACHE;
@@ -17692,7 +17780,7 @@ function scanAnnotation(tokens, atIndex) {
17692
17780
  };
17693
17781
  }
17694
17782
  function rangeWithin(outer, inner) {
17695
- return positionCompare(outer.start, inner.start) <= 0 && positionCompare(inner.end, outer.end) <= 0;
17783
+ return positionCompare2(outer.start, inner.start) <= 0 && positionCompare2(inner.end, outer.end) <= 0;
17696
17784
  }
17697
17785
  var init_project_source_registry = __esm({
17698
17786
  "../packages/neoscript-language/src/project-source-registry.ts"() {
@@ -26441,7 +26529,8 @@ function collectDeclarationSymbols(uri, document, declaration, symbols, diagnost
26441
26529
  void 0,
26442
26530
  member.type.name,
26443
26531
  member.kind === "function",
26444
- member.docsText
26532
+ member.docsText,
26533
+ member.modifiers.includes("static")
26445
26534
  );
26446
26535
  if (member.kind === "function") {
26447
26536
  for (const parameter4 of member.parameters) {
@@ -26531,7 +26620,7 @@ function collectFlowSymbols(uri, content, ownerName, scopeRange, symbols, diagno
26531
26620
  }
26532
26621
  }
26533
26622
  }
26534
- function collectSymbol(uri, kind, name, range2, annotations, ownerName, symbols, diagnostics, scopeRange, detail, callable2, documentation) {
26623
+ function collectSymbol(uri, kind, name, range2, annotations, ownerName, symbols, diagnostics, scopeRange, detail, callable2, documentation, staticMember) {
26535
26624
  if (RESERVED_NAMES3.has(name)) {
26536
26625
  diagnostics.push({
26537
26626
  uri,
@@ -26561,6 +26650,7 @@ function collectSymbol(uri, kind, name, range2, annotations, ownerName, symbols,
26561
26650
  ...scopeRange ? { scopeRange } : {},
26562
26651
  ...detail ? { detail } : {},
26563
26652
  ...callable2 ? { callable: callable2 } : {},
26653
+ ...staticMember ? { static: true } : {},
26564
26654
  ...documentation ? { documentation } : {},
26565
26655
  location: { uri, range: range2 }
26566
26656
  });
@@ -27345,16 +27435,16 @@ function projectAnnotationNamesAt(analysis, document, position) {
27345
27435
  const containing = declarationContaining(source, position);
27346
27436
  if (containing?.kind === "enum") return ["id"];
27347
27437
  if (containing?.kind === "class" || containing?.kind === "interface") {
27348
- if (positionCompare(position, containing.nameRange.start) < 0) {
27438
+ if (positionCompare2(position, containing.nameRange.start) < 0) {
27349
27439
  return projectDeclarationAnnotationNames(containing);
27350
27440
  }
27351
27441
  const member = containing.members.find(
27352
- (candidate) => rangeContains(candidate.range, position) || positionCompare(position, candidate.range.start) <= 0
27442
+ (candidate) => rangeContains(candidate.range, position) || positionCompare2(position, candidate.range.start) <= 0
27353
27443
  );
27354
27444
  return projectMemberAnnotationNames(member);
27355
27445
  }
27356
27446
  const following = source.declarations.find(
27357
- (declaration) => positionCompare(position, declaration.range.start) <= 0
27447
+ (declaration) => positionCompare2(position, declaration.range.start) <= 0
27358
27448
  );
27359
27449
  if (following) return projectDeclarationAnnotationNames(following);
27360
27450
  return ["id"];
@@ -27696,7 +27786,10 @@ function sourceTypeAt(document, position) {
27696
27786
  }
27697
27787
  function projectMemberCompletions(analysis, document, position) {
27698
27788
  const tokens = projectTokens(document).filter(
27699
- (token) => token.kind !== "eof" && token.kind !== "comment" && positionCompare(token.range.start, position) <= 0
27789
+ (token) => token.kind !== "eof" && token.kind !== "comment" && // Auto-inserted/future punctuation at the cursor has not been authored
27790
+ // yet for completion purposes. Including a semicolon that starts at the
27791
+ // cursor makes `Device.|;` lose the dot it is completing.
27792
+ positionCompare2(token.range.start, position) < 0
27700
27793
  );
27701
27794
  let cursor = tokens.length - 1;
27702
27795
  const current = tokens[cursor];
@@ -27708,16 +27801,19 @@ function projectMemberCompletions(analysis, document, position) {
27708
27801
  if (!owner || owner.kind !== "identifier") return null;
27709
27802
  const resolvedOwner = projectSymbolAt(analysis, document, owner.range.start);
27710
27803
  if (!resolvedOwner) return null;
27711
- const ownerNames = /* @__PURE__ */ new Set();
27712
- const typeName = projectSymbolTypeName(resolvedOwner.symbol);
27713
- if (typeName) ownerNames.add(typeName);
27714
- if (resolvedOwner.symbol.kind === "global") {
27715
- ownerNames.add(resolvedOwner.symbol.name);
27804
+ const ownerNames = projectReceiverOwnerNames(analysis, resolvedOwner.symbol);
27805
+ if (ownerNames.length === 0) return null;
27806
+ const members = [];
27807
+ const seenNames = /* @__PURE__ */ new Set();
27808
+ for (const ownerName of ownerNames) {
27809
+ for (const symbol of analysis.symbols) {
27810
+ if (symbol.kind !== "member" && symbol.kind !== "graphChild" && symbol.kind !== "enumOption" || symbol.ownerName !== ownerName || !projectQualifiedSymbolMatchesReceiver(symbol, resolvedOwner.symbol) || seenNames.has(symbol.name)) {
27811
+ continue;
27812
+ }
27813
+ seenNames.add(symbol.name);
27814
+ members.push(symbol);
27815
+ }
27716
27816
  }
27717
- if (ownerNames.size === 0) return null;
27718
- const members = analysis.symbols.filter(
27719
- (symbol) => (symbol.kind === "member" || symbol.kind === "graphChild") && symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
27720
- );
27721
27817
  return members.length > 0 ? members : null;
27722
27818
  }
27723
27819
  function projectConstructorArgumentCompletions(analysis, document, position) {
@@ -27860,7 +27956,7 @@ function projectConstructionIndex(analysis) {
27860
27956
  }
27861
27957
  function projectDottedPathBefore(document, position) {
27862
27958
  const tokens = projectTokens(document).filter(
27863
- (token) => token.kind !== "eof" && token.kind !== "comment" && positionCompare(token.range.start, position) < 0
27959
+ (token) => token.kind !== "eof" && token.kind !== "comment" && positionCompare2(token.range.start, position) < 0
27864
27960
  );
27865
27961
  let cursor = tokens.length - 1;
27866
27962
  const current = tokens[cursor];
@@ -27987,7 +28083,7 @@ function projectGraphTargetCompletions(analysis, document, position) {
27987
28083
  const declaration = source ? declarationContaining(source, position) : void 0;
27988
28084
  if (declaration?.kind !== "class") return null;
27989
28085
  const member = declaration.members.filter(
27990
- (candidate) => positionCompare(candidate.range.start, position) <= 0
28086
+ (candidate) => positionCompare2(candidate.range.start, position) <= 0
27991
28087
  ).at(-1);
27992
28088
  if (member?.kind !== "field" || !member.flowInitializer) return null;
27993
28089
  return analysis.symbols.filter(
@@ -28315,6 +28411,9 @@ function projectInlayHints(analysis, document, range2) {
28315
28411
  if (!argumentToken || argumentToken.named) continue;
28316
28412
  const name = parameterNames[argument2];
28317
28413
  if (!name) continue;
28414
+ if (argumentToken.identifier && normalizeProjectHintName(argumentToken.identifier) === normalizeProjectHintName(name)) {
28415
+ continue;
28416
+ }
28318
28417
  if (range2 && !rangeContains(range2, argumentToken.start)) continue;
28319
28418
  hints.push({
28320
28419
  position: argumentToken.start,
@@ -28334,6 +28433,10 @@ function projectInlayHints(analysis, document, range2) {
28334
28433
  }
28335
28434
  return hints;
28336
28435
  }
28436
+ function projectInferredTypeRefactorings(analysis, document, range2) {
28437
+ const snapshot = projectBodySnapshotAt(analysis, document, range2.start) ?? projectBodySnapshotAt(analysis, document, range2.end);
28438
+ return snapshot ? inferredTypeRefactorings(snapshot, range2) : [];
28439
+ }
28337
28440
  function projectCallParameterNames(analysis, document, callee, beforeCallee) {
28338
28441
  if (callee.text === "Reference") return null;
28339
28442
  if (callee.text === "Pause") {
@@ -28380,7 +28483,8 @@ function projectCallArguments(tokens, openIndex) {
28380
28483
  if (depth === 0 && atArgumentStart) {
28381
28484
  result.push({
28382
28485
  start: token.range.start,
28383
- named: token.kind === "identifier" && tokens[index + 1]?.text === ":"
28486
+ named: token.kind === "identifier" && tokens[index + 1]?.text === ":",
28487
+ ...token.kind === "identifier" && (tokens[index + 1]?.text === "," || tokens[index + 1]?.text === ")") ? { identifier: token.text } : {}
28384
28488
  });
28385
28489
  atArgumentStart = false;
28386
28490
  }
@@ -28392,6 +28496,9 @@ function projectCallArguments(tokens, openIndex) {
28392
28496
  }
28393
28497
  return result;
28394
28498
  }
28499
+ function normalizeProjectHintName(name) {
28500
+ return name.replaceAll("_", "").toLocaleLowerCase();
28501
+ }
28395
28502
  function positionKey(position) {
28396
28503
  return `${position.line}:${position.character}`;
28397
28504
  }
@@ -28576,7 +28683,7 @@ function projectSemanticTokens(document, analysis) {
28576
28683
  );
28577
28684
  }
28578
28685
  return result.sort(
28579
- (left, right) => positionCompare(left.range.start, right.range.start)
28686
+ (left, right) => positionCompare2(left.range.start, right.range.start)
28580
28687
  );
28581
28688
  }
28582
28689
  function isProjectContextualKeyword(tokens, tokenIndex) {
@@ -28607,7 +28714,8 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28607
28714
  (candidate) => candidate.start === token.start && candidate.end === token.end
28608
28715
  );
28609
28716
  const source = analysis.documents.get(document.uri);
28610
- if (source && sourceTypeAt(source, token.range.start) || sourceTokens[tokenIndex - 1]?.text === "new") {
28717
+ const typePosition = source && sourceTypeAt(source, token.range.start);
28718
+ if (typePosition || sourceTokens[tokenIndex - 1]?.text === "new") {
28611
28719
  const types = candidates.filter(
28612
28720
  (symbol) => symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter"
28613
28721
  );
@@ -28620,10 +28728,15 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28620
28728
  );
28621
28729
  if (scoped.length > 0) return { token, symbol: scoped[0] };
28622
28730
  const visibleCandidates = candidates.filter((symbol) => !symbol.scopeRange);
28623
- if (visibleCandidates.length === 1) {
28624
- return { token, symbol: visibleCandidates[0] };
28625
- }
28626
28731
  if (visibleCandidates.length === 0) return null;
28732
+ const contextualEnum = contextualProjectEnumOptionSymbol(
28733
+ analysis,
28734
+ document,
28735
+ token,
28736
+ visibleCandidates,
28737
+ source
28738
+ );
28739
+ if (contextualEnum) return { token, symbol: contextualEnum };
28627
28740
  if (sourceTokens[tokenIndex + 1]?.text === "=" && sourceTokens[tokenIndex + 2]?.text !== "=") {
28628
28741
  const constructed = constructionMemberSymbol(
28629
28742
  analysis,
@@ -28634,7 +28747,7 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28634
28747
  if (constructed) return { token, symbol: constructed };
28635
28748
  }
28636
28749
  const owner = source ? declarationContaining(source, token.range.start)?.name : void 0;
28637
- if (owner) {
28750
+ if (owner && sourceTokens[tokenIndex - 1]?.text !== ".") {
28638
28751
  const owned = visibleCandidates.filter(
28639
28752
  (symbol) => symbol.ownerName === owner
28640
28753
  );
@@ -28648,24 +28761,100 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28648
28761
  dotOwner.range.start,
28649
28762
  index
28650
28763
  );
28651
- const ownerNames = /* @__PURE__ */ new Set();
28652
- if (resolvedOwner) {
28653
- const typeName = projectSymbolTypeName(resolvedOwner.symbol);
28654
- if (typeName) ownerNames.add(typeName);
28655
- if (resolvedOwner.symbol.kind === "global") {
28656
- ownerNames.add(resolvedOwner.symbol.name);
28657
- }
28658
- } else {
28659
- ownerNames.add(dotOwner.text);
28660
- }
28764
+ const ownerNames = resolvedOwner ? projectReceiverOwnerNames(analysis, resolvedOwner.symbol) : [dotOwner.text];
28661
28765
  const qualified = visibleCandidates.filter(
28662
- (symbol) => symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
28766
+ (symbol) => symbol.ownerName !== void 0 && ownerNames.includes(symbol.ownerName) && (resolvedOwner === null || projectQualifiedSymbolMatchesReceiver(symbol, resolvedOwner.symbol))
28663
28767
  );
28664
- if (qualified.length === 1) return { token, symbol: qualified[0] };
28768
+ for (const ownerName of ownerNames) {
28769
+ const nearest = qualified.filter(
28770
+ (symbol) => symbol.ownerName === ownerName
28771
+ );
28772
+ if (nearest.length === 1) return { token, symbol: nearest[0] };
28773
+ if (nearest.length > 1) return null;
28774
+ }
28775
+ return null;
28665
28776
  }
28666
- const projectLevel = visibleCandidates.filter((symbol) => !symbol.ownerName);
28777
+ const values = visibleCandidates.filter(
28778
+ (symbol) => !isProjectTypeSymbol(symbol)
28779
+ );
28780
+ if (values.length === 1) return { token, symbol: values[0] };
28781
+ const projectLevel = (values.length > 0 ? values : visibleCandidates).filter(
28782
+ (symbol) => !symbol.ownerName
28783
+ );
28667
28784
  return projectLevel.length === 1 ? { token, symbol: projectLevel[0] } : null;
28668
28785
  }
28786
+ function isProjectTypeSymbol(symbol) {
28787
+ return symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter";
28788
+ }
28789
+ function projectQualifiedSymbolMatchesReceiver(symbol, receiver) {
28790
+ if (isProjectTypeSymbol(receiver)) {
28791
+ if (receiver.kind === "enum") return symbol.kind === "enumOption";
28792
+ return symbol.kind === "member" && symbol.static === true;
28793
+ }
28794
+ if (symbol.kind === "enumOption") return false;
28795
+ return symbol.kind !== "member" || symbol.static !== true;
28796
+ }
28797
+ function projectReceiverOwnerNames(analysis, receiver) {
28798
+ const result = [];
28799
+ const visitedTypes = /* @__PURE__ */ new Set();
28800
+ const appendTypeAndBases = (typeName2) => {
28801
+ if (visitedTypes.has(typeName2)) return;
28802
+ visitedTypes.add(typeName2);
28803
+ if (!result.includes(typeName2)) result.push(typeName2);
28804
+ const declaration = findTypeDeclaration(analysis, typeName2);
28805
+ if (declaration?.kind !== "class" && declaration?.kind !== "interface") {
28806
+ return;
28807
+ }
28808
+ for (const base of declaration.baseTypes) {
28809
+ const resolved = findTypeDeclaration(analysis, base.name);
28810
+ if (resolved?.kind === "class" || resolved?.kind === "interface") {
28811
+ appendTypeAndBases(base.name);
28812
+ }
28813
+ }
28814
+ };
28815
+ if (receiver.kind === "global") {
28816
+ result.push(receiver.name);
28817
+ }
28818
+ const typeName = projectSymbolTypeName(receiver);
28819
+ if (typeName) appendTypeAndBases(typeName);
28820
+ return result;
28821
+ }
28822
+ function contextualProjectEnumOptionSymbol(analysis, document, token, candidates, source) {
28823
+ if (!isContextualDot(document.text, token.start)) return null;
28824
+ const expectedTypeName = constructionSiteAt(analysis, document, token.range.start)?.expectedTypeName ?? (source ? parameterDefaultTypeNameAt(source, token.range.start) : null);
28825
+ if (!expectedTypeName) return null;
28826
+ const options = candidates.filter(
28827
+ (symbol) => symbol.kind === "enumOption" && symbol.ownerName === expectedTypeName
28828
+ );
28829
+ return options.length === 1 ? options[0] : null;
28830
+ }
28831
+ function parameterDefaultTypeNameAt(document, position) {
28832
+ const fromParameters = (parameters) => {
28833
+ const parameter4 = parameters?.find(
28834
+ (candidate) => candidate.defaultValue && rangeContains(candidate.defaultValue.range, position)
28835
+ );
28836
+ return parameter4?.type.name ?? null;
28837
+ };
28838
+ for (const declaration of document.declarations) {
28839
+ if (declaration.kind !== "class" && declaration.kind !== "interface") {
28840
+ continue;
28841
+ }
28842
+ if (declaration.kind === "class") {
28843
+ const header = fromParameters(declaration.headerParameters);
28844
+ if (header) return header;
28845
+ for (const constructor2 of declaration.constructors) {
28846
+ const constructorParameter = fromParameters(constructor2.parameters);
28847
+ if (constructorParameter) return constructorParameter;
28848
+ }
28849
+ }
28850
+ for (const member of declaration.members) {
28851
+ if (member.kind !== "function") continue;
28852
+ const functionParameter = fromParameters(member.parameters);
28853
+ if (functionParameter) return functionParameter;
28854
+ }
28855
+ }
28856
+ return null;
28857
+ }
28669
28858
  function constructionMemberSymbol(analysis, document, token, candidates) {
28670
28859
  const site = constructionSiteAt(analysis, document, token.range.start);
28671
28860
  let current = site?.enclosingTypeName ?? null;
@@ -28702,7 +28891,7 @@ function projectSymbolTypeName(symbol) {
28702
28891
  }
28703
28892
  function declarationContaining(document, position) {
28704
28893
  return document.declarations.find(
28705
- (declaration) => positionCompare(declaration.range.start, position) <= 0 && positionCompare(position, declaration.range.end) <= 0
28894
+ (declaration) => positionCompare2(declaration.range.start, position) <= 0 && positionCompare2(position, declaration.range.end) <= 0
28706
28895
  );
28707
28896
  }
28708
28897
  function sameProjectSymbol(left, right) {
@@ -28714,14 +28903,14 @@ function sameProjectSymbol(left, right) {
28714
28903
  }
28715
28904
  function tokensBeforePosition(text, position) {
28716
28905
  return lex(text).tokens.filter(
28717
- (token) => token.kind !== "comment" && token.kind !== "eof" && positionCompare(token.range.start, position) < 0
28906
+ (token) => token.kind !== "comment" && token.kind !== "eof" && positionCompare2(token.range.start, position) < 0
28718
28907
  );
28719
28908
  }
28720
28909
  function activeCallOpenIndex(tokens, position) {
28721
28910
  let closeDepth = 0;
28722
28911
  for (let index = tokens.length - 1; index >= 0; index--) {
28723
28912
  const token = tokens[index];
28724
- if (!token || positionCompare(token.range.start, position) > 0) continue;
28913
+ if (!token || positionCompare2(token.range.start, position) > 0) continue;
28725
28914
  if (token.text === ")") {
28726
28915
  closeDepth++;
28727
28916
  continue;
@@ -28740,7 +28929,7 @@ function activeCallParameter(tokens, openIndex, position) {
28740
28929
  let parameter4 = 0;
28741
28930
  for (let index = openIndex + 1; index < tokens.length; index++) {
28742
28931
  const token = tokens[index];
28743
- if (!token || positionCompare(token.range.start, position) >= 0) break;
28932
+ if (!token || positionCompare2(token.range.start, position) >= 0) break;
28744
28933
  if (token.text === "(" || token.text === "[" || token.text === "{") {
28745
28934
  depth++;
28746
28935
  continue;
@@ -28823,7 +29012,7 @@ function constructionSiteAt(analysis, document, position) {
28823
29012
  const root = initializerRootAt(analysis, document, position);
28824
29013
  if (!root) return null;
28825
29014
  const tokens = projectTokens(document).filter(
28826
- (token) => token.kind !== "comment" && token.kind !== "eof" && rangeContains(root.range, token.range.start) && positionCompare(token.range.start, position) < 0
29015
+ (token) => token.kind !== "comment" && token.kind !== "eof" && rangeContains(root.range, token.range.start) && positionCompare2(token.range.start, position) < 0
28827
29016
  );
28828
29017
  const stack = [];
28829
29018
  const expectedType = () => {
@@ -32051,6 +32240,154 @@ var init_project_schema_manifest = __esm({
32051
32240
  }
32052
32241
  });
32053
32242
 
32243
+ // ../packages/neoscript-language/src/spelling-diagnostics.ts
32244
+ function enhanceSpellingDiagnostics(document, diagnostics, completionAt) {
32245
+ return diagnostics.map((diagnostic) => {
32246
+ const misspelling = diagnosticMisspelling(diagnostic);
32247
+ if (!misspelling) return diagnostic;
32248
+ const token = diagnosticIdentifierToken(
32249
+ document,
32250
+ diagnostic,
32251
+ misspelling.name
32252
+ );
32253
+ if (!token) return diagnostic;
32254
+ const candidates = spellingCandidates(
32255
+ misspelling,
32256
+ completionAt(token.range.start)
32257
+ );
32258
+ if (candidates.length === 0) return diagnostic;
32259
+ return {
32260
+ ...diagnostic,
32261
+ message: `${diagnostic.message} ${didYouMean(candidates)}`,
32262
+ suggestions: candidates
32263
+ };
32264
+ });
32265
+ }
32266
+ function spellingQuickFixes(document, diagnostic) {
32267
+ const misspelling = diagnosticMisspelling(diagnostic);
32268
+ if (!misspelling || !diagnostic.suggestions) return [];
32269
+ const token = diagnosticIdentifierToken(
32270
+ document,
32271
+ diagnostic,
32272
+ misspelling.name
32273
+ );
32274
+ if (!token) return [];
32275
+ return diagnostic.suggestions.filter((suggestion) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(suggestion)).map((suggestion, index) => ({
32276
+ title: `Change '${misspelling.name}' to '${suggestion}'`,
32277
+ kind: "quickfix",
32278
+ diagnostics: [diagnostic],
32279
+ edit: {
32280
+ changes: {
32281
+ [document.uri]: [{ range: token.range, newText: suggestion }]
32282
+ }
32283
+ },
32284
+ preferred: index === 0
32285
+ }));
32286
+ }
32287
+ function diagnosticMisspelling(diagnostic) {
32288
+ if (/to initialize\b/.test(diagnostic.message)) return null;
32289
+ const unknown = /Unknown (identifier|function) '([^']+)'/.exec(
32290
+ diagnostic.message
32291
+ );
32292
+ if (unknown?.[2]) {
32293
+ return {
32294
+ name: unknown[2],
32295
+ kind: unknown[1] === "function" ? "callable" : "value"
32296
+ };
32297
+ }
32298
+ const member = /has no (?:static )?(?:callable )?member '([^']+)'/.exec(
32299
+ diagnostic.message
32300
+ );
32301
+ if (!member?.[1]) return null;
32302
+ return {
32303
+ name: member[1],
32304
+ kind: /callable member/.test(member[0]) ? "callable" : "member"
32305
+ };
32306
+ }
32307
+ function diagnosticIdentifierToken(document, diagnostic, name) {
32308
+ const candidates = projectTokens(document).filter(
32309
+ (token) => (token.kind === "identifier" || token.kind === "type") && token.text === name
32310
+ );
32311
+ if (candidates.length === 0) return null;
32312
+ const target = positionScore(diagnostic.range.start);
32313
+ return candidates.sort(
32314
+ (left, right) => Math.abs(positionScore(left.range.start) - target) - Math.abs(positionScore(right.range.start) - target)
32315
+ )[0] ?? null;
32316
+ }
32317
+ function positionScore(position) {
32318
+ return position.line * 1e6 + position.character;
32319
+ }
32320
+ function spellingCandidates(misspelling, items) {
32321
+ const allowedKinds = misspelling.kind === "callable" ? /* @__PURE__ */ new Set(["method", "function"]) : misspelling.kind === "member" ? /* @__PURE__ */ new Set(["field", "property", "method", "function", "enumMember"]) : /* @__PURE__ */ new Set([
32322
+ "variable",
32323
+ "parameter",
32324
+ "field",
32325
+ "property",
32326
+ "enumMember",
32327
+ "value"
32328
+ ]);
32329
+ const original = misspelling.name.toLocaleLowerCase();
32330
+ const maximumDistance = original.length <= 4 ? 1 : original.length <= 8 ? 2 : 3;
32331
+ const ranked = /* @__PURE__ */ new Map();
32332
+ for (const item of items) {
32333
+ if (!allowedKinds.has(item.kind)) continue;
32334
+ const candidate = item.label.replace(/^\./, "");
32335
+ if (candidate.toLocaleLowerCase() === original) continue;
32336
+ const distance = damerauLevenshtein(
32337
+ original,
32338
+ candidate.toLocaleLowerCase()
32339
+ );
32340
+ if (distance > maximumDistance) continue;
32341
+ const prior = ranked.get(candidate);
32342
+ if (prior === void 0 || distance < prior)
32343
+ ranked.set(candidate, distance);
32344
+ }
32345
+ return [...ranked].sort(
32346
+ ([left, leftDistance], [right, rightDistance]) => leftDistance - rightDistance || left.localeCompare(right)
32347
+ ).slice(0, 3).map(([candidate]) => candidate);
32348
+ }
32349
+ function damerauLevenshtein(left, right) {
32350
+ const rows = left.length + 1;
32351
+ const columns = right.length + 1;
32352
+ const distance = Array.from(
32353
+ { length: rows },
32354
+ () => Array(columns).fill(0)
32355
+ );
32356
+ for (let row = 0; row < rows; row++) distance[row][0] = row;
32357
+ for (let column = 0; column < columns; column++) {
32358
+ distance[0][column] = column;
32359
+ }
32360
+ for (let row = 1; row < rows; row++) {
32361
+ for (let column = 1; column < columns; column++) {
32362
+ const substitution = left[row - 1] === right[column - 1] ? 0 : 1;
32363
+ distance[row][column] = Math.min(
32364
+ distance[row - 1][column] + 1,
32365
+ distance[row][column - 1] + 1,
32366
+ distance[row - 1][column - 1] + substitution
32367
+ );
32368
+ if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) {
32369
+ distance[row][column] = Math.min(
32370
+ distance[row][column],
32371
+ distance[row - 2][column - 2] + 1
32372
+ );
32373
+ }
32374
+ }
32375
+ }
32376
+ return distance[left.length][right.length];
32377
+ }
32378
+ function didYouMean(candidates) {
32379
+ const quoted = candidates.map((candidate) => `'${candidate}'`);
32380
+ if (quoted.length === 1) return `Did you mean ${quoted[0]}?`;
32381
+ if (quoted.length === 2) return `Did you mean ${quoted[0]} or ${quoted[1]}?`;
32382
+ return `Did you mean ${quoted.slice(0, -1).join(", ")}, or ${quoted.at(-1)}?`;
32383
+ }
32384
+ var init_spelling_diagnostics = __esm({
32385
+ "../packages/neoscript-language/src/spelling-diagnostics.ts"() {
32386
+ "use strict";
32387
+ init_project_source_tokens();
32388
+ }
32389
+ });
32390
+
32054
32391
  // ../packages/neoscript-language/src/service.ts
32055
32392
  function createNeoScriptLanguageService() {
32056
32393
  return new LanguageService();
@@ -32145,6 +32482,7 @@ var init_service = __esm({
32145
32482
  init_project_source_language_features();
32146
32483
  init_project_source_tokens();
32147
32484
  init_quick_fixes();
32485
+ init_spelling_diagnostics();
32148
32486
  init_syntax();
32149
32487
  init_test_prelude();
32150
32488
  LanguageService = class {
@@ -32234,14 +32572,17 @@ var init_service = __esm({
32234
32572
  if (isProjectDocument(state.document, state.context)) {
32235
32573
  const project = this.projectAnalysis();
32236
32574
  return {
32237
- diagnostics: projectDiagnostics(project, uri),
32575
+ diagnostics: this.enhanceDiagnostics(
32576
+ state,
32577
+ projectDiagnostics(project, uri)
32578
+ ),
32238
32579
  symbols: projectDocumentSymbols(state.document, project),
32239
32580
  semanticTokens: projectSemanticTokens(state.document, project)
32240
32581
  };
32241
32582
  }
32242
32583
  const snapshot = this.scriptSnapshot(state);
32243
32584
  return {
32244
- diagnostics: finalizeDiagnostics(uri, [
32585
+ diagnostics: this.enhanceDiagnostics(state, [
32245
32586
  ...snapshot.parsed.diagnostics,
32246
32587
  ...semanticDiagnostics(snapshot)
32247
32588
  ]),
@@ -32254,7 +32595,10 @@ var init_service = __esm({
32254
32595
  if (signal?.aborted) return [];
32255
32596
  const generation = ++state.validationGeneration;
32256
32597
  if (isProjectDocument(state.document, state.context)) {
32257
- const diagnostics = projectDiagnostics(this.projectAnalysis(), uri);
32598
+ const diagnostics = this.enhanceDiagnostics(
32599
+ state,
32600
+ projectDiagnostics(this.projectAnalysis(), uri)
32601
+ );
32258
32602
  if (signal?.aborted) return [];
32259
32603
  const current2 = this.documents.get(uri);
32260
32604
  if (!current2 || current2.validationGeneration !== generation) return [];
@@ -32272,7 +32616,7 @@ var init_service = __esm({
32272
32616
  if (signal?.aborted) return [];
32273
32617
  const current = this.documents.get(uri);
32274
32618
  if (!current || current.validationGeneration !== generation) return [];
32275
- return finalizeDiagnostics(uri, [
32619
+ return this.enhanceDiagnostics(state, [
32276
32620
  ...snapshot.parsed.diagnostics,
32277
32621
  ...strictDiagnostics
32278
32622
  ]);
@@ -32385,10 +32729,15 @@ var init_service = __esm({
32385
32729
  ...isProjectDocument(state.document, state.context) ? {} : { kind: options?.kind ?? state.context.kind }
32386
32730
  });
32387
32731
  }
32388
- codeActions(uri, diagnostics) {
32732
+ codeActions(uri, diagnostics, range2) {
32389
32733
  const state = this.requireState(uri);
32390
32734
  const actions = [];
32391
32735
  const project = isProjectDocument(state.document, state.context) ? this.projectAnalysis() : null;
32736
+ if (range2) {
32737
+ actions.push(
32738
+ ...project ? projectInferredTypeRefactorings(project, state.document, range2) : inferredTypeRefactorings(this.scriptSnapshot(state), range2)
32739
+ );
32740
+ }
32392
32741
  for (const diagnostic of finalizeDiagnostics(uri, diagnostics)) {
32393
32742
  if (project) {
32394
32743
  actions.push(
@@ -32398,6 +32747,7 @@ var init_service = __esm({
32398
32747
  ...projectConstructionQuickFixes(project, state.document, diagnostic)
32399
32748
  );
32400
32749
  }
32750
+ actions.push(...spellingQuickFixes(state.document, diagnostic));
32401
32751
  const edit = quickFixFor(state.document, diagnostic);
32402
32752
  if (!edit) continue;
32403
32753
  actions.push({
@@ -32410,6 +32760,17 @@ var init_service = __esm({
32410
32760
  }
32411
32761
  return deduplicateCodeActions(actions);
32412
32762
  }
32763
+ /** Adds conservative, completion-backed spelling suggestions to diagnostics. */
32764
+ enhanceDiagnostics(state, diagnostics) {
32765
+ return finalizeDiagnostics(
32766
+ state.document.uri,
32767
+ enhanceSpellingDiagnostics(
32768
+ state.document,
32769
+ diagnostics,
32770
+ (position) => this.complete(state.document.uri, position).items
32771
+ )
32772
+ );
32773
+ }
32413
32774
  projectAnalysis() {
32414
32775
  if (this.cachedProjectAnalysis?.generation === this.projectGeneration) {
32415
32776
  return this.cachedProjectAnalysis.analysis;
@@ -110651,7 +111012,7 @@ var init_registry2 = __esm({
110651
111012
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
110652
111013
  formatVersion: 3,
110653
111014
  contractVersion: "3.12",
110654
- cliVersion: "0.31.4",
111015
+ cliVersion: "0.31.6",
110655
111016
  projectFileUploadBatchSize: 32,
110656
111017
  documentRecords: {
110657
111018
  member: {
@@ -117246,7 +117607,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
117246
117607
  async function main() {
117247
117608
  const args = parseArgs(process.argv.slice(2));
117248
117609
  if (args.command === "--version") {
117249
- console.log("0.31.4");
117610
+ console.log("0.31.6");
117250
117611
  return;
117251
117612
  }
117252
117613
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.31.4",
3
+ "version": "0.31.6",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.31.4 -->
12
+ <!-- reviewed-through-cli: 0.31.6 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.31.4 -->
86
+ <!-- reviewed-through-cli: 0.31.6 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale