@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
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
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
|
|
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
|
|
17676
|
+
return positionCompare2(left.start, right.start) === 0 && positionCompare2(left.end, right.end) === 0;
|
|
17621
17677
|
}
|
|
17622
|
-
function
|
|
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
|
|
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 (
|
|
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) ||
|
|
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) =>
|
|
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" &&
|
|
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 =
|
|
27744
|
-
|
|
27745
|
-
|
|
27746
|
-
|
|
27747
|
-
|
|
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" &&
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
28766
|
+
(symbol) => symbol.ownerName !== void 0 && ownerNames.includes(symbol.ownerName) && (resolvedOwner === null || projectQualifiedSymbolMatchesReceiver(symbol, resolvedOwner.symbol))
|
|
28695
28767
|
);
|
|
28696
|
-
|
|
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
|
|
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) =>
|
|
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" &&
|
|
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 ||
|
|
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 ||
|
|
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) &&
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
@@ -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.
|
|
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
|