@neocompose/cli 0.31.5 → 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,20 @@
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
+
3
18
  ## [0.31.5] - 2026-08-15
4
19
 
5
20
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -2484,14 +2484,19 @@ function signatureHelp(snapshot, position) {
2484
2484
  function inlayHints(snapshot, range2) {
2485
2485
  const hints = [];
2486
2486
  for (const call of snapshot.parsed.calls) {
2487
- if (call.kind !== "constructor") continue;
2488
- const type = snapshot.project.typeByName.get(call.name);
2489
- 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);
2490
2492
  if (!parameterNames) continue;
2491
2493
  for (let index = 0; index < call.argumentRanges.length; index++) {
2492
2494
  const argumentRange = call.argumentRanges[index];
2493
2495
  const parameterName = parameterNames[index];
2494
2496
  if (!argumentRange || !parameterName) continue;
2497
+ if (obviousParameterName(snapshot, argumentRange, parameterName)) {
2498
+ continue;
2499
+ }
2495
2500
  const position = firstNonWhitespacePosition(snapshot, argumentRange);
2496
2501
  if (range2 && !positionInRange(position, range2)) continue;
2497
2502
  hints.push({
@@ -2502,8 +2507,59 @@ function inlayHints(snapshot, range2) {
2502
2507
  });
2503
2508
  }
2504
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
+ }
2505
2523
  return hints;
2506
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
+ }
2507
2563
  function constructorParameterNames(type) {
2508
2564
  const declared = type?.declaredConstructors ?? [];
2509
2565
  if (declared.length > 0) {
@@ -17489,7 +17545,7 @@ var init_project_source_parser = __esm({
17489
17545
 
17490
17546
  // ../packages/neoscript-language/src/project-source-tokens.ts
17491
17547
  function rangeContains(range2, position) {
17492
- return positionCompare(range2.start, position) <= 0 && positionCompare(position, range2.end) <= 0;
17548
+ return positionCompare2(range2.start, position) <= 0 && positionCompare2(position, range2.end) <= 0;
17493
17549
  }
17494
17550
  function rangeSize(range2) {
17495
17551
  return (range2.end.line - range2.start.line) * 1e6 + range2.end.character - range2.start.character;
@@ -17617,9 +17673,9 @@ function tripleInterpolationEnd(text, start, limit) {
17617
17673
  return null;
17618
17674
  }
17619
17675
  function rangesEqual(left, right) {
17620
- 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;
17621
17677
  }
17622
- function positionCompare(left, right) {
17678
+ function positionCompare2(left, right) {
17623
17679
  return left.line === right.line ? left.character - right.character : left.line - right.line;
17624
17680
  }
17625
17681
  var PROJECT_TOKEN_CACHE;
@@ -17724,7 +17780,7 @@ function scanAnnotation(tokens, atIndex) {
17724
17780
  };
17725
17781
  }
17726
17782
  function rangeWithin(outer, inner) {
17727
- 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;
17728
17784
  }
17729
17785
  var init_project_source_registry = __esm({
17730
17786
  "../packages/neoscript-language/src/project-source-registry.ts"() {
@@ -26473,7 +26529,8 @@ function collectDeclarationSymbols(uri, document, declaration, symbols, diagnost
26473
26529
  void 0,
26474
26530
  member.type.name,
26475
26531
  member.kind === "function",
26476
- member.docsText
26532
+ member.docsText,
26533
+ member.modifiers.includes("static")
26477
26534
  );
26478
26535
  if (member.kind === "function") {
26479
26536
  for (const parameter4 of member.parameters) {
@@ -26563,7 +26620,7 @@ function collectFlowSymbols(uri, content, ownerName, scopeRange, symbols, diagno
26563
26620
  }
26564
26621
  }
26565
26622
  }
26566
- 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) {
26567
26624
  if (RESERVED_NAMES3.has(name)) {
26568
26625
  diagnostics.push({
26569
26626
  uri,
@@ -26593,6 +26650,7 @@ function collectSymbol(uri, kind, name, range2, annotations, ownerName, symbols,
26593
26650
  ...scopeRange ? { scopeRange } : {},
26594
26651
  ...detail ? { detail } : {},
26595
26652
  ...callable2 ? { callable: callable2 } : {},
26653
+ ...staticMember ? { static: true } : {},
26596
26654
  ...documentation ? { documentation } : {},
26597
26655
  location: { uri, range: range2 }
26598
26656
  });
@@ -27377,16 +27435,16 @@ function projectAnnotationNamesAt(analysis, document, position) {
27377
27435
  const containing = declarationContaining(source, position);
27378
27436
  if (containing?.kind === "enum") return ["id"];
27379
27437
  if (containing?.kind === "class" || containing?.kind === "interface") {
27380
- if (positionCompare(position, containing.nameRange.start) < 0) {
27438
+ if (positionCompare2(position, containing.nameRange.start) < 0) {
27381
27439
  return projectDeclarationAnnotationNames(containing);
27382
27440
  }
27383
27441
  const member = containing.members.find(
27384
- (candidate) => rangeContains(candidate.range, position) || positionCompare(position, candidate.range.start) <= 0
27442
+ (candidate) => rangeContains(candidate.range, position) || positionCompare2(position, candidate.range.start) <= 0
27385
27443
  );
27386
27444
  return projectMemberAnnotationNames(member);
27387
27445
  }
27388
27446
  const following = source.declarations.find(
27389
- (declaration) => positionCompare(position, declaration.range.start) <= 0
27447
+ (declaration) => positionCompare2(position, declaration.range.start) <= 0
27390
27448
  );
27391
27449
  if (following) return projectDeclarationAnnotationNames(following);
27392
27450
  return ["id"];
@@ -27728,7 +27786,10 @@ function sourceTypeAt(document, position) {
27728
27786
  }
27729
27787
  function projectMemberCompletions(analysis, document, position) {
27730
27788
  const tokens = projectTokens(document).filter(
27731
- (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
27732
27793
  );
27733
27794
  let cursor = tokens.length - 1;
27734
27795
  const current = tokens[cursor];
@@ -27740,16 +27801,19 @@ function projectMemberCompletions(analysis, document, position) {
27740
27801
  if (!owner || owner.kind !== "identifier") return null;
27741
27802
  const resolvedOwner = projectSymbolAt(analysis, document, owner.range.start);
27742
27803
  if (!resolvedOwner) return null;
27743
- const ownerNames = /* @__PURE__ */ new Set();
27744
- const typeName = projectSymbolTypeName(resolvedOwner.symbol);
27745
- if (typeName) ownerNames.add(typeName);
27746
- if (resolvedOwner.symbol.kind === "global") {
27747
- 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
+ }
27748
27816
  }
27749
- if (ownerNames.size === 0) return null;
27750
- const members = analysis.symbols.filter(
27751
- (symbol) => (symbol.kind === "member" || symbol.kind === "graphChild") && symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
27752
- );
27753
27817
  return members.length > 0 ? members : null;
27754
27818
  }
27755
27819
  function projectConstructorArgumentCompletions(analysis, document, position) {
@@ -27892,7 +27956,7 @@ function projectConstructionIndex(analysis) {
27892
27956
  }
27893
27957
  function projectDottedPathBefore(document, position) {
27894
27958
  const tokens = projectTokens(document).filter(
27895
- (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
27896
27960
  );
27897
27961
  let cursor = tokens.length - 1;
27898
27962
  const current = tokens[cursor];
@@ -28019,7 +28083,7 @@ function projectGraphTargetCompletions(analysis, document, position) {
28019
28083
  const declaration = source ? declarationContaining(source, position) : void 0;
28020
28084
  if (declaration?.kind !== "class") return null;
28021
28085
  const member = declaration.members.filter(
28022
- (candidate) => positionCompare(candidate.range.start, position) <= 0
28086
+ (candidate) => positionCompare2(candidate.range.start, position) <= 0
28023
28087
  ).at(-1);
28024
28088
  if (member?.kind !== "field" || !member.flowInitializer) return null;
28025
28089
  return analysis.symbols.filter(
@@ -28347,6 +28411,9 @@ function projectInlayHints(analysis, document, range2) {
28347
28411
  if (!argumentToken || argumentToken.named) continue;
28348
28412
  const name = parameterNames[argument2];
28349
28413
  if (!name) continue;
28414
+ if (argumentToken.identifier && normalizeProjectHintName(argumentToken.identifier) === normalizeProjectHintName(name)) {
28415
+ continue;
28416
+ }
28350
28417
  if (range2 && !rangeContains(range2, argumentToken.start)) continue;
28351
28418
  hints.push({
28352
28419
  position: argumentToken.start,
@@ -28366,6 +28433,10 @@ function projectInlayHints(analysis, document, range2) {
28366
28433
  }
28367
28434
  return hints;
28368
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
+ }
28369
28440
  function projectCallParameterNames(analysis, document, callee, beforeCallee) {
28370
28441
  if (callee.text === "Reference") return null;
28371
28442
  if (callee.text === "Pause") {
@@ -28412,7 +28483,8 @@ function projectCallArguments(tokens, openIndex) {
28412
28483
  if (depth === 0 && atArgumentStart) {
28413
28484
  result.push({
28414
28485
  start: token.range.start,
28415
- 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 } : {}
28416
28488
  });
28417
28489
  atArgumentStart = false;
28418
28490
  }
@@ -28424,6 +28496,9 @@ function projectCallArguments(tokens, openIndex) {
28424
28496
  }
28425
28497
  return result;
28426
28498
  }
28499
+ function normalizeProjectHintName(name) {
28500
+ return name.replaceAll("_", "").toLocaleLowerCase();
28501
+ }
28427
28502
  function positionKey(position) {
28428
28503
  return `${position.line}:${position.character}`;
28429
28504
  }
@@ -28608,7 +28683,7 @@ function projectSemanticTokens(document, analysis) {
28608
28683
  );
28609
28684
  }
28610
28685
  return result.sort(
28611
- (left, right) => positionCompare(left.range.start, right.range.start)
28686
+ (left, right) => positionCompare2(left.range.start, right.range.start)
28612
28687
  );
28613
28688
  }
28614
28689
  function isProjectContextualKeyword(tokens, tokenIndex) {
@@ -28639,7 +28714,8 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28639
28714
  (candidate) => candidate.start === token.start && candidate.end === token.end
28640
28715
  );
28641
28716
  const source = analysis.documents.get(document.uri);
28642
- 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") {
28643
28719
  const types = candidates.filter(
28644
28720
  (symbol) => symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter"
28645
28721
  );
@@ -28652,10 +28728,15 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28652
28728
  );
28653
28729
  if (scoped.length > 0) return { token, symbol: scoped[0] };
28654
28730
  const visibleCandidates = candidates.filter((symbol) => !symbol.scopeRange);
28655
- if (visibleCandidates.length === 1) {
28656
- return { token, symbol: visibleCandidates[0] };
28657
- }
28658
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 };
28659
28740
  if (sourceTokens[tokenIndex + 1]?.text === "=" && sourceTokens[tokenIndex + 2]?.text !== "=") {
28660
28741
  const constructed = constructionMemberSymbol(
28661
28742
  analysis,
@@ -28666,7 +28747,7 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28666
28747
  if (constructed) return { token, symbol: constructed };
28667
28748
  }
28668
28749
  const owner = source ? declarationContaining(source, token.range.start)?.name : void 0;
28669
- if (owner) {
28750
+ if (owner && sourceTokens[tokenIndex - 1]?.text !== ".") {
28670
28751
  const owned = visibleCandidates.filter(
28671
28752
  (symbol) => symbol.ownerName === owner
28672
28753
  );
@@ -28680,24 +28761,100 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
28680
28761
  dotOwner.range.start,
28681
28762
  index
28682
28763
  );
28683
- const ownerNames = /* @__PURE__ */ new Set();
28684
- if (resolvedOwner) {
28685
- const typeName = projectSymbolTypeName(resolvedOwner.symbol);
28686
- if (typeName) ownerNames.add(typeName);
28687
- if (resolvedOwner.symbol.kind === "global") {
28688
- ownerNames.add(resolvedOwner.symbol.name);
28689
- }
28690
- } else {
28691
- ownerNames.add(dotOwner.text);
28692
- }
28764
+ const ownerNames = resolvedOwner ? projectReceiverOwnerNames(analysis, resolvedOwner.symbol) : [dotOwner.text];
28693
28765
  const qualified = visibleCandidates.filter(
28694
- (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))
28695
28767
  );
28696
- 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;
28697
28776
  }
28698
- 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
+ );
28699
28784
  return projectLevel.length === 1 ? { token, symbol: projectLevel[0] } : null;
28700
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
+ }
28701
28858
  function constructionMemberSymbol(analysis, document, token, candidates) {
28702
28859
  const site = constructionSiteAt(analysis, document, token.range.start);
28703
28860
  let current = site?.enclosingTypeName ?? null;
@@ -28734,7 +28891,7 @@ function projectSymbolTypeName(symbol) {
28734
28891
  }
28735
28892
  function declarationContaining(document, position) {
28736
28893
  return document.declarations.find(
28737
- (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
28738
28895
  );
28739
28896
  }
28740
28897
  function sameProjectSymbol(left, right) {
@@ -28746,14 +28903,14 @@ function sameProjectSymbol(left, right) {
28746
28903
  }
28747
28904
  function tokensBeforePosition(text, position) {
28748
28905
  return lex(text).tokens.filter(
28749
- (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
28750
28907
  );
28751
28908
  }
28752
28909
  function activeCallOpenIndex(tokens, position) {
28753
28910
  let closeDepth = 0;
28754
28911
  for (let index = tokens.length - 1; index >= 0; index--) {
28755
28912
  const token = tokens[index];
28756
- if (!token || positionCompare(token.range.start, position) > 0) continue;
28913
+ if (!token || positionCompare2(token.range.start, position) > 0) continue;
28757
28914
  if (token.text === ")") {
28758
28915
  closeDepth++;
28759
28916
  continue;
@@ -28772,7 +28929,7 @@ function activeCallParameter(tokens, openIndex, position) {
28772
28929
  let parameter4 = 0;
28773
28930
  for (let index = openIndex + 1; index < tokens.length; index++) {
28774
28931
  const token = tokens[index];
28775
- if (!token || positionCompare(token.range.start, position) >= 0) break;
28932
+ if (!token || positionCompare2(token.range.start, position) >= 0) break;
28776
28933
  if (token.text === "(" || token.text === "[" || token.text === "{") {
28777
28934
  depth++;
28778
28935
  continue;
@@ -28855,7 +29012,7 @@ function constructionSiteAt(analysis, document, position) {
28855
29012
  const root = initializerRootAt(analysis, document, position);
28856
29013
  if (!root) return null;
28857
29014
  const tokens = projectTokens(document).filter(
28858
- (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
28859
29016
  );
28860
29017
  const stack = [];
28861
29018
  const expectedType = () => {
@@ -32083,6 +32240,154 @@ var init_project_schema_manifest = __esm({
32083
32240
  }
32084
32241
  });
32085
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
+
32086
32391
  // ../packages/neoscript-language/src/service.ts
32087
32392
  function createNeoScriptLanguageService() {
32088
32393
  return new LanguageService();
@@ -32177,6 +32482,7 @@ var init_service = __esm({
32177
32482
  init_project_source_language_features();
32178
32483
  init_project_source_tokens();
32179
32484
  init_quick_fixes();
32485
+ init_spelling_diagnostics();
32180
32486
  init_syntax();
32181
32487
  init_test_prelude();
32182
32488
  LanguageService = class {
@@ -32266,14 +32572,17 @@ var init_service = __esm({
32266
32572
  if (isProjectDocument(state.document, state.context)) {
32267
32573
  const project = this.projectAnalysis();
32268
32574
  return {
32269
- diagnostics: projectDiagnostics(project, uri),
32575
+ diagnostics: this.enhanceDiagnostics(
32576
+ state,
32577
+ projectDiagnostics(project, uri)
32578
+ ),
32270
32579
  symbols: projectDocumentSymbols(state.document, project),
32271
32580
  semanticTokens: projectSemanticTokens(state.document, project)
32272
32581
  };
32273
32582
  }
32274
32583
  const snapshot = this.scriptSnapshot(state);
32275
32584
  return {
32276
- diagnostics: finalizeDiagnostics(uri, [
32585
+ diagnostics: this.enhanceDiagnostics(state, [
32277
32586
  ...snapshot.parsed.diagnostics,
32278
32587
  ...semanticDiagnostics(snapshot)
32279
32588
  ]),
@@ -32286,7 +32595,10 @@ var init_service = __esm({
32286
32595
  if (signal?.aborted) return [];
32287
32596
  const generation = ++state.validationGeneration;
32288
32597
  if (isProjectDocument(state.document, state.context)) {
32289
- const diagnostics = projectDiagnostics(this.projectAnalysis(), uri);
32598
+ const diagnostics = this.enhanceDiagnostics(
32599
+ state,
32600
+ projectDiagnostics(this.projectAnalysis(), uri)
32601
+ );
32290
32602
  if (signal?.aborted) return [];
32291
32603
  const current2 = this.documents.get(uri);
32292
32604
  if (!current2 || current2.validationGeneration !== generation) return [];
@@ -32304,7 +32616,7 @@ var init_service = __esm({
32304
32616
  if (signal?.aborted) return [];
32305
32617
  const current = this.documents.get(uri);
32306
32618
  if (!current || current.validationGeneration !== generation) return [];
32307
- return finalizeDiagnostics(uri, [
32619
+ return this.enhanceDiagnostics(state, [
32308
32620
  ...snapshot.parsed.diagnostics,
32309
32621
  ...strictDiagnostics
32310
32622
  ]);
@@ -32417,10 +32729,15 @@ var init_service = __esm({
32417
32729
  ...isProjectDocument(state.document, state.context) ? {} : { kind: options?.kind ?? state.context.kind }
32418
32730
  });
32419
32731
  }
32420
- codeActions(uri, diagnostics) {
32732
+ codeActions(uri, diagnostics, range2) {
32421
32733
  const state = this.requireState(uri);
32422
32734
  const actions = [];
32423
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
+ }
32424
32741
  for (const diagnostic of finalizeDiagnostics(uri, diagnostics)) {
32425
32742
  if (project) {
32426
32743
  actions.push(
@@ -32430,6 +32747,7 @@ var init_service = __esm({
32430
32747
  ...projectConstructionQuickFixes(project, state.document, diagnostic)
32431
32748
  );
32432
32749
  }
32750
+ actions.push(...spellingQuickFixes(state.document, diagnostic));
32433
32751
  const edit = quickFixFor(state.document, diagnostic);
32434
32752
  if (!edit) continue;
32435
32753
  actions.push({
@@ -32442,6 +32760,17 @@ var init_service = __esm({
32442
32760
  }
32443
32761
  return deduplicateCodeActions(actions);
32444
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
+ }
32445
32774
  projectAnalysis() {
32446
32775
  if (this.cachedProjectAnalysis?.generation === this.projectGeneration) {
32447
32776
  return this.cachedProjectAnalysis.analysis;
@@ -110683,7 +111012,7 @@ var init_registry2 = __esm({
110683
111012
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
110684
111013
  formatVersion: 3,
110685
111014
  contractVersion: "3.12",
110686
- cliVersion: "0.31.5",
111015
+ cliVersion: "0.31.6",
110687
111016
  projectFileUploadBatchSize: 32,
110688
111017
  documentRecords: {
110689
111018
  member: {
@@ -117278,7 +117607,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
117278
117607
  async function main() {
117279
117608
  const args = parseArgs(process.argv.slice(2));
117280
117609
  if (args.command === "--version") {
117281
- console.log("0.31.5");
117610
+ console.log("0.31.6");
117282
117611
  return;
117283
117612
  }
117284
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.5",
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.5 -->
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.5 -->
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