@neocompose/cli 0.26.1 → 0.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neo.mjs CHANGED
@@ -169,6 +169,9 @@ function warn(text) {
169
169
  function note(text) {
170
170
  console.log(color.dim(text));
171
171
  }
172
+ function pad(text, width) {
173
+ return text.length >= width ? text : text + " ".repeat(width - text.length);
174
+ }
172
175
  function paintChangeKind(kind) {
173
176
  const painter = CHANGE_KIND_COLOR[kind];
174
177
  return painter === void 0 ? kind : painter(kind);
@@ -2143,16 +2146,28 @@ function complete(snapshot, position) {
2143
2146
  candidates = switchCaseCandidates;
2144
2147
  } else if (tail && (tail.kind === "punctuation" && tail.text === "." || tail.kind === "operator" && tail.text === "?.")) {
2145
2148
  const receiverTokens = expressionTokensBefore(tokens, tokens.length - 1);
2146
- const resolved = resolveChain(snapshot, receiverTokens, offset);
2147
- candidates = completionItemsForResolution(snapshot, resolved, word);
2149
+ if (receiverTokens.length === 0 || isContextualCompletionDot(snapshot.source.text, word.start)) {
2150
+ candidates = contextualEnumCompletionItems(
2151
+ snapshot,
2152
+ expectedTypeAt(snapshot, word.start),
2153
+ word
2154
+ );
2155
+ } else {
2156
+ const resolved = resolveChain(snapshot, receiverTokens, offset);
2157
+ candidates = completionItemsForResolution(snapshot, resolved, word);
2158
+ }
2148
2159
  } else if (tail?.kind === "identifier" && tail.text === NEOSCRIPT_CONSTRUCTOR_KEYWORD) {
2149
- candidates = constructorCompletionItems(snapshot, word);
2160
+ candidates = constructorCompletionItems(
2161
+ snapshot,
2162
+ word,
2163
+ expectedTypeAt(snapshot, word.start)
2164
+ );
2150
2165
  } else if (isTypePosition(tokens)) {
2151
2166
  candidates = typeCompletionItems(snapshot, word);
2152
2167
  } else if (isLambdaParameterPosition(tokens)) {
2153
2168
  candidates = [];
2154
2169
  } else {
2155
- candidates = topLevelCompletionItems(snapshot, offset, word);
2170
+ candidates = contextualCompletionItems(snapshot, offset, word);
2156
2171
  }
2157
2172
  return {
2158
2173
  isIncomplete: hasUnclosedConstruct(snapshot.lexed.tokens, offset),
@@ -2230,12 +2245,33 @@ function signatureHelp(snapshot, position) {
2230
2245
  if (call.kind === "constructor") {
2231
2246
  const type = resolution?.staticType;
2232
2247
  const constructor2 = type?.constructorSignature;
2233
- if (!type || !constructor2) return null;
2248
+ const declared = type?.declaredConstructors ?? [];
2249
+ if (!type) return null;
2234
2250
  const activeParameter2 = countTopLevelCommas(
2235
2251
  snapshot.source.text,
2236
2252
  call.openParenOffset + 1,
2237
2253
  offset
2238
2254
  );
2255
+ if (declared.length > 0) {
2256
+ return {
2257
+ signatures: declared.map((candidate) => ({
2258
+ label: `${type.name}(${candidate.parameters.map(
2259
+ (parameter4) => `${formatType(parameter4.type, snapshot.project)} ${parameter4.name}`
2260
+ ).join(", ")})`,
2261
+ ...candidate.documentation ? { documentation: candidate.documentation } : {},
2262
+ parameters: candidate.parameters.map((parameter4) => ({
2263
+ label: `${formatType(parameter4.type, snapshot.project)} ${parameter4.name}`,
2264
+ ...parameter4.documentation ? { documentation: parameter4.documentation } : {}
2265
+ }))
2266
+ })),
2267
+ activeSignature: 0,
2268
+ activeParameter: Math.min(
2269
+ activeParameter2,
2270
+ Math.max(0, (declared[0]?.parameters.length ?? 1) - 1)
2271
+ )
2272
+ };
2273
+ }
2274
+ if (!constructor2) return null;
2239
2275
  return {
2240
2276
  signatures: [
2241
2277
  {
@@ -2283,19 +2319,18 @@ function inlayHints(snapshot, range2) {
2283
2319
  const hints = [];
2284
2320
  for (const call of snapshot.parsed.calls) {
2285
2321
  if (call.kind !== "constructor") continue;
2286
- const constructor2 = snapshot.project.typeByName.get(
2287
- call.name
2288
- )?.constructorSignature;
2289
- if (!constructor2) continue;
2322
+ const type = snapshot.project.typeByName.get(call.name);
2323
+ const parameterNames = constructorParameterNames(type);
2324
+ if (!parameterNames) continue;
2290
2325
  for (let index = 0; index < call.argumentRanges.length; index++) {
2291
2326
  const argumentRange = call.argumentRanges[index];
2292
- const parameter4 = constructor2.parameters[index];
2293
- if (!argumentRange || !parameter4) continue;
2327
+ const parameterName = parameterNames[index];
2328
+ if (!argumentRange || !parameterName) continue;
2294
2329
  const position = firstNonWhitespacePosition(snapshot, argumentRange);
2295
2330
  if (range2 && !positionInRange(position, range2)) continue;
2296
2331
  hints.push({
2297
2332
  position,
2298
- label: `${parameter4.name}:`,
2333
+ label: `${parameterName}:`,
2299
2334
  kind: "parameter",
2300
2335
  paddingRight: true
2301
2336
  });
@@ -2303,6 +2338,22 @@ function inlayHints(snapshot, range2) {
2303
2338
  }
2304
2339
  return hints;
2305
2340
  }
2341
+ function constructorParameterNames(type) {
2342
+ const declared = type?.declaredConstructors ?? [];
2343
+ if (declared.length > 0) {
2344
+ const arity = Math.max(
2345
+ ...declared.map((constructor2) => constructor2.parameters.length)
2346
+ );
2347
+ return Array.from({ length: arity }, (_, index) => {
2348
+ const names = new Set(
2349
+ declared.map((constructor2) => constructor2.parameters[index]?.name)
2350
+ );
2351
+ const [name] = names;
2352
+ return names.size === 1 && name ? name : null;
2353
+ });
2354
+ }
2355
+ return type?.constructorSignature?.parameters.map((parameter4) => parameter4.name) ?? null;
2356
+ }
2306
2357
  function documentSymbols(snapshot) {
2307
2358
  const unitSymbols = snapshot.parsed.units.map((unit) => ({
2308
2359
  name: unit.kind,
@@ -2439,8 +2490,9 @@ function isValidNeoIdentifier(name) {
2439
2490
  function isValidRenameIdentifier(name) {
2440
2491
  return isValidNeoIdentifier(name);
2441
2492
  }
2442
- function topLevelCompletionItems(snapshot, offset, word) {
2493
+ function contextualCompletionItems(snapshot, offset, word) {
2443
2494
  const items = [];
2495
+ const expectedType = expectedTypeAt(snapshot, word.start);
2444
2496
  if (insideCatchBody(snapshot, offset)) {
2445
2497
  items.push(
2446
2498
  completion(
@@ -2454,7 +2506,12 @@ function topLevelCompletionItems(snapshot, offset, word) {
2454
2506
  )
2455
2507
  );
2456
2508
  }
2509
+ const statementStart = isStatementStart(snapshot.lexed.tokens, word.start);
2457
2510
  for (const keyword of NEOSCRIPT_KEYWORDS) {
2511
+ if (!statementStart && statementOnlyKeyword(keyword)) continue;
2512
+ if (!statementStart && expectedType !== null && !literalKeywordMatchesExpectedType(keyword, expectedType)) {
2513
+ continue;
2514
+ }
2458
2515
  items.push(
2459
2516
  completion(
2460
2517
  keyword,
@@ -2467,6 +2524,7 @@ function topLevelCompletionItems(snapshot, offset, word) {
2467
2524
  );
2468
2525
  }
2469
2526
  for (const snippet of NEOSCRIPT_STATEMENT_SNIPPETS) {
2527
+ if (!statementStart) continue;
2470
2528
  items.push(
2471
2529
  completion(
2472
2530
  snippet.label,
@@ -2479,60 +2537,282 @@ function topLevelCompletionItems(snapshot, offset, word) {
2479
2537
  )
2480
2538
  );
2481
2539
  }
2482
- items.push(
2483
- completion(
2484
- NEOSCRIPT_INFERRED_LOCAL_KEYWORD,
2485
- "snippet",
2486
- "var ${1:name} = ${0:value};",
2487
- "Inferred local declaration",
2488
- word,
2489
- snapshot,
2490
- "snippet"
2491
- )
2492
- );
2493
- items.push(
2494
- completion(
2495
- NEOSCRIPT_CONSTRUCTOR_KEYWORD,
2496
- "snippet",
2497
- "new ${1:ClassName}(${0})",
2498
- "Construct a Class value",
2499
- word,
2500
- snapshot,
2501
- "snippet"
2502
- )
2503
- );
2504
- for (const primitive3 of NEOSCRIPT_PRIMITIVE_TYPES) {
2540
+ if (statementStart) {
2505
2541
  items.push(
2506
2542
  completion(
2507
- primitive3,
2508
- "keyword",
2509
- primitive3,
2510
- "Primitive type",
2543
+ NEOSCRIPT_INFERRED_LOCAL_KEYWORD,
2544
+ "snippet",
2545
+ "var ${1:name} = ${0:value};",
2546
+ "Inferred local declaration",
2511
2547
  word,
2512
- snapshot
2548
+ snapshot,
2549
+ "snippet"
2550
+ )
2551
+ );
2552
+ }
2553
+ const expectedNamed = expectedType?.kind === "named" ? snapshot.project.typeById.get(expectedType.typeId) : void 0;
2554
+ if (expectedType === null || expectedNamed?.kind === "class" || expectedNamed?.kind === "interface") {
2555
+ items.push(
2556
+ completion(
2557
+ NEOSCRIPT_CONSTRUCTOR_KEYWORD,
2558
+ "snippet",
2559
+ "new ${1:ClassName}(${0})",
2560
+ "Construct a Class value",
2561
+ word,
2562
+ snapshot,
2563
+ "snippet"
2564
+ )
2565
+ );
2566
+ }
2567
+ if (statementStart && expectedType === null) {
2568
+ for (const primitive3 of NEOSCRIPT_PRIMITIVE_TYPES) {
2569
+ items.push(
2570
+ completion(
2571
+ primitive3,
2572
+ "keyword",
2573
+ primitive3,
2574
+ "Primitive type",
2575
+ word,
2576
+ snapshot
2577
+ )
2578
+ );
2579
+ }
2580
+ items.push(
2581
+ completion(
2582
+ "Dictionary",
2583
+ "snippet",
2584
+ "Dictionary<string, ${1:T}>",
2585
+ "Dictionary type",
2586
+ word,
2587
+ snapshot,
2588
+ "snippet"
2513
2589
  )
2514
2590
  );
2515
2591
  }
2516
- items.push(
2517
- completion(
2518
- "Dictionary",
2519
- "snippet",
2520
- "Dictionary<string, ${1:T}>",
2521
- "Dictionary type",
2522
- word,
2523
- snapshot,
2524
- "snippet"
2525
- )
2526
- );
2527
2592
  for (const symbol of scopeAt(snapshot, offset)) {
2593
+ if (expectedType !== null && !isNeoScriptTypeAssignable(
2594
+ symbol.returnType ?? symbol.type,
2595
+ expectedType,
2596
+ snapshot.project
2597
+ )) {
2598
+ continue;
2599
+ }
2528
2600
  items.push(symbolCompletion(symbol, word, snapshot));
2529
2601
  }
2530
- for (const type of snapshot.project.typeByName.values()) {
2531
- if (type.kind === "builtin" && type.name.startsWith("__")) continue;
2532
- items.push(typeCompletion(type, word, snapshot));
2602
+ if (statementStart && expectedType === null) {
2603
+ for (const type of snapshot.project.typeByName.values()) {
2604
+ if (type.kind === "builtin" && type.name.startsWith("__")) continue;
2605
+ items.push(typeCompletion(type, word, snapshot));
2606
+ }
2533
2607
  }
2534
2608
  return items;
2535
2609
  }
2610
+ function contextualEnumCompletionItems(snapshot, expected, word) {
2611
+ if (expected?.kind !== "named") return [];
2612
+ const type = snapshot.project.typeById.get(expected.typeId);
2613
+ if (type?.kind !== "enum") return [];
2614
+ return type.members.filter((member) => member.kind === "enumMember").map((member) => {
2615
+ const item = symbolCompletion(member, word, snapshot);
2616
+ return {
2617
+ ...item,
2618
+ label: `.${member.name}`,
2619
+ insertText: member.name,
2620
+ textEdit: {
2621
+ range: snapshot.source.range(word.start, word.end),
2622
+ newText: member.name
2623
+ }
2624
+ };
2625
+ });
2626
+ }
2627
+ function isContextualCompletionDot(text, wordStart) {
2628
+ if (text[wordStart - 1] !== ".") return false;
2629
+ let cursor = wordStart - 2;
2630
+ while (cursor >= 0 && /\s/.test(text[cursor] ?? "")) cursor--;
2631
+ const beforeDot = text[cursor];
2632
+ if (beforeDot !== void 0 && /[A-Za-z0-9_]/.test(beforeDot)) {
2633
+ const precedingWord = /[A-Za-z_][A-Za-z0-9_]*$/.exec(
2634
+ text.slice(0, cursor + 1)
2635
+ )?.[0];
2636
+ return precedingWord === "return" || precedingWord === "case";
2637
+ }
2638
+ return beforeDot === void 0 || !/[A-Za-z0-9_\])}]/.test(beforeDot);
2639
+ }
2640
+ function expectedTypeAt(snapshot, offset) {
2641
+ const call = [...snapshot.parsed.calls].filter(
2642
+ (candidate) => candidate.openParenOffset < offset && (candidate.closeParenOffset === void 0 || candidate.closeParenOffset >= offset)
2643
+ ).sort((left, right) => right.openParenOffset - left.openParenOffset)[0];
2644
+ if (call) {
2645
+ const reference2 = snapshot.parsed.references.find(
2646
+ (candidate) => candidate.range.start.line === call.nameRange.start.line && candidate.range.start.character === call.nameRange.start.character
2647
+ );
2648
+ const resolution = reference2 ? resolveReference(snapshot, reference2) : null;
2649
+ const index = countTopLevelCommas(
2650
+ snapshot.source.text,
2651
+ call.openParenOffset + 1,
2652
+ offset
2653
+ );
2654
+ const parameterType = call.kind === "constructor" ? constructorParameterTypeAt(resolution?.staticType, index, snapshot) : resolution?.symbol?.parameters?.[index]?.type;
2655
+ if (parameterType) {
2656
+ return expectedCollectionElementType(
2657
+ parameterType,
2658
+ snapshot.lexed.tokens,
2659
+ call.openParenOffset + 1,
2660
+ offset
2661
+ );
2662
+ }
2663
+ }
2664
+ const local = snapshot.parsed.locals.filter((candidate) => {
2665
+ const first = candidate.initializerTokens?.[0];
2666
+ return !candidate.inferred && first !== void 0 && first.start <= offset && offset <= snapshot.source.offsetAt(candidate.declarationRange.end);
2667
+ }).sort((left, right) => right.scopeStart - left.scopeStart)[0];
2668
+ if (local) {
2669
+ const first = local.initializerTokens?.[0];
2670
+ return expectedCollectionElementType(
2671
+ resolveDeclaredType(local, snapshot),
2672
+ snapshot.lexed.tokens,
2673
+ first?.start ?? offset,
2674
+ offset
2675
+ );
2676
+ }
2677
+ const tokens = significantTokensBefore(snapshot.lexed.tokens, offset);
2678
+ const boundary = lastStatementBoundary(tokens);
2679
+ for (let index = tokens.length - 1; index > boundary; index--) {
2680
+ const token = tokens[index];
2681
+ if (token?.kind !== "operator" || token.text !== "=") continue;
2682
+ const left = expressionTokensBefore(tokens, index);
2683
+ const resolution = resolveChain(snapshot, left, offset);
2684
+ if (resolution) {
2685
+ return expectedCollectionElementType(
2686
+ resolution.type,
2687
+ tokens,
2688
+ token.end,
2689
+ offset
2690
+ );
2691
+ }
2692
+ break;
2693
+ }
2694
+ for (let index = tokens.length - 1; index > boundary; index--) {
2695
+ const token = tokens[index];
2696
+ if (token?.kind !== "operator" || token.text !== "==" && token.text !== "!=") {
2697
+ continue;
2698
+ }
2699
+ const left = expressionTokensBefore(tokens, index);
2700
+ const type = resolveChain(snapshot, left, offset)?.type ?? inferExpressionType(left, snapshot, left[0]?.start ?? offset);
2701
+ if (!isUnknownExpectedType(type)) return type;
2702
+ }
2703
+ const futureComparison = expectedTypeFromComparisonRight(snapshot, offset);
2704
+ if (futureComparison) return futureComparison;
2705
+ const tail = tokens.at(-1);
2706
+ if (tail?.kind === "operator" && ["!", "&&", "||"].includes(tail.text) || tail?.kind === "punctuation" && tail.text === "(" && tokens.at(-2)?.kind === "keyword" && ["if", "while"].includes(tokens.at(-2)?.text ?? "")) {
2707
+ return { kind: "primitive", name: "bool" };
2708
+ }
2709
+ const throwToken = tokens.slice(boundary + 1).find((token) => token.kind === "keyword" && token.text === "throw");
2710
+ if (throwToken) return { kind: "primitive", name: "string" };
2711
+ const returnToken = tokens.slice(boundary + 1).find((token) => token.kind === "keyword" && token.text === "return");
2712
+ if (returnToken && snapshot.context.returnType) {
2713
+ return expectedCollectionElementType(
2714
+ snapshot.context.returnType,
2715
+ tokens,
2716
+ returnToken.end,
2717
+ offset
2718
+ );
2719
+ }
2720
+ return null;
2721
+ }
2722
+ function expectedTypeFromComparisonRight(snapshot, offset) {
2723
+ const tokens = snapshot.lexed.tokens.filter(
2724
+ (token) => token.kind !== "comment" && token.kind !== "eof"
2725
+ );
2726
+ let operatorIndex = -1;
2727
+ for (let index = 0; index < tokens.length; index++) {
2728
+ const token = tokens[index];
2729
+ if (!token || token.start < offset) continue;
2730
+ if (token.kind === "punctuation" && [";", "{", "}"].includes(token.text)) {
2731
+ break;
2732
+ }
2733
+ if (token.kind === "operator" && (token.text === "==" || token.text === "!=")) {
2734
+ operatorIndex = index;
2735
+ break;
2736
+ }
2737
+ }
2738
+ if (operatorIndex < 0) return null;
2739
+ const right = [];
2740
+ let depth = 0;
2741
+ for (let index = operatorIndex + 1; index < tokens.length; index++) {
2742
+ const token = tokens[index];
2743
+ if (!token) continue;
2744
+ if (token.kind === "punctuation" && ["(", "["].includes(token.text)) {
2745
+ depth++;
2746
+ } else if (token.kind === "punctuation" && [")", "]"].includes(token.text)) {
2747
+ if (depth === 0) break;
2748
+ depth--;
2749
+ }
2750
+ if (depth === 0 && token.kind === "punctuation" && [";", ",", "{"].includes(token.text)) {
2751
+ break;
2752
+ }
2753
+ right.push(token);
2754
+ }
2755
+ const type = inferExpressionType(right, snapshot, right[0]?.start ?? offset);
2756
+ return isUnknownExpectedType(type) ? null : type;
2757
+ }
2758
+ function isUnknownExpectedType(type) {
2759
+ return type.kind === "primitive" && type.name === "unknown";
2760
+ }
2761
+ function constructorParameterTypeAt(type, index, snapshot) {
2762
+ const declared = (type?.declaredConstructors ?? []).map((constructor2) => constructor2.parameters[index]?.type).filter(
2763
+ (candidate) => candidate !== void 0
2764
+ );
2765
+ if ((type?.declaredConstructors?.length ?? 0) === 0) {
2766
+ return type?.constructorSignature?.parameters[index]?.parameterType;
2767
+ }
2768
+ if (declared.length === 0) return void 0;
2769
+ const first = declared[0];
2770
+ return declared.every(
2771
+ (candidate) => isNeoScriptTypeAssignable(candidate, first, snapshot.project) && isNeoScriptTypeAssignable(first, candidate, snapshot.project)
2772
+ ) ? first : void 0;
2773
+ }
2774
+ function expectedCollectionElementType(initial, tokens, start, end) {
2775
+ let expected = initial;
2776
+ const stack = [];
2777
+ for (const token of tokens) {
2778
+ if (token.start < start || token.end > end || token.kind !== "punctuation") {
2779
+ continue;
2780
+ }
2781
+ if (token.text === "[") {
2782
+ stack.push(expected);
2783
+ if (expected.kind === "list" || expected.kind === "set") {
2784
+ expected = expected.elementType;
2785
+ }
2786
+ } else if (token.text === "]") {
2787
+ expected = stack.pop() ?? expected;
2788
+ }
2789
+ }
2790
+ return expected;
2791
+ }
2792
+ function lastStatementBoundary(tokens) {
2793
+ for (let index = tokens.length - 1; index >= 0; index--) {
2794
+ const token = tokens[index];
2795
+ if (token?.kind === "punctuation" && (token.text === ";" || token.text === "{" || token.text === "}")) {
2796
+ return index;
2797
+ }
2798
+ }
2799
+ return -1;
2800
+ }
2801
+ function isStatementStart(tokens, offset) {
2802
+ const significant = significantTokensBefore(tokens, offset);
2803
+ const tail = significant.at(-1);
2804
+ return tail === void 0 || tail.kind === "punctuation" && (tail.text === ";" || tail.text === "{" || tail.text === "}") || tail.kind === "punctuation" && tail.text === ":";
2805
+ }
2806
+ function statementOnlyKeyword(keyword) {
2807
+ return !["true", "false", "null", "is"].includes(keyword);
2808
+ }
2809
+ function literalKeywordMatchesExpectedType(keyword, expected) {
2810
+ if (keyword === "true" || keyword === "false") {
2811
+ return expected.kind === "primitive" && expected.name === "bool";
2812
+ }
2813
+ if (keyword === "null") return expected.nullable === true;
2814
+ return false;
2815
+ }
2536
2816
  function catchFilterCompletionItems(snapshot, offset, word) {
2537
2817
  const prefix = snapshot.source.text.slice(0, offset);
2538
2818
  if (!/\bcatch\s*\(\s*string\s+[A-Za-z_][A-Za-z0-9_]*\s*\)\s*$/.test(prefix)) {
@@ -2682,16 +2962,24 @@ function typeCompletionItems(snapshot, word) {
2682
2962
  }
2683
2963
  return items;
2684
2964
  }
2685
- function constructorCompletionItems(snapshot, word) {
2965
+ function constructorCompletionItems(snapshot, word, expected = null) {
2686
2966
  return [...snapshot.project.typeByName.values()].flatMap((type) => {
2687
2967
  const signature = type.constructorSignature;
2688
- if (!signature) return [];
2968
+ const declared = type.declaredConstructors ?? [];
2969
+ if (!signature && declared.length === 0) return [];
2970
+ if (expected !== null && !isNeoScriptTypeAssignable(
2971
+ { kind: "named", typeId: type.id },
2972
+ expected,
2973
+ snapshot.project
2974
+ )) {
2975
+ return [];
2976
+ }
2689
2977
  const insertText = `${type.name}(`;
2690
2978
  return [
2691
2979
  {
2692
2980
  label: type.name,
2693
2981
  kind: "class",
2694
- detail: formatConstructorSignature(type.name, signature, snapshot),
2982
+ detail: declared.length > 0 ? `${type.name} \u2014 ${declared.length} declared constructor${declared.length === 1 ? "" : "s"}` : signature ? formatConstructorSignature(type.name, signature, snapshot) : type.name,
2695
2983
  ...type.documentation ? { documentation: type.documentation } : {},
2696
2984
  insertText,
2697
2985
  textEdit: {
@@ -2764,6 +3052,22 @@ function membersForType(snapshot, type) {
2764
3052
  return [];
2765
3053
  }
2766
3054
  function resolveReference(snapshot, reference2) {
3055
+ if (!reference2.member && (isTypeAnnotationReference(snapshot, reference2) || isConstructorTypeReference(snapshot, reference2))) {
3056
+ const type = snapshot.project.typeByName.get(reference2.name);
3057
+ if (type) {
3058
+ return {
3059
+ type: { kind: "named", typeId: type.id },
3060
+ staticType: type
3061
+ };
3062
+ }
3063
+ }
3064
+ if (reference2.member && isContextualCompletionDot(snapshot.source.text, reference2.start)) {
3065
+ const contextual = resolveContextualExpectedEnumReference(
3066
+ snapshot,
3067
+ reference2
3068
+ );
3069
+ if (contextual) return contextual;
3070
+ }
2767
3071
  const tokens = snapshot.lexed.tokens.filter(
2768
3072
  (token) => token.kind !== "comment" && token.kind !== "eof" && token.end <= reference2.end
2769
3073
  );
@@ -2814,6 +3118,16 @@ function resolveContextualSwitchEnumReference(snapshot, reference2) {
2814
3118
  );
2815
3119
  return symbol ? { type: symbol.type, symbol } : null;
2816
3120
  }
3121
+ function resolveContextualExpectedEnumReference(snapshot, reference2) {
3122
+ const expected = expectedTypeAt(snapshot, reference2.start);
3123
+ if (expected?.kind !== "named") return null;
3124
+ const type = snapshot.project.typeById.get(expected.typeId);
3125
+ if (type?.kind !== "enum") return null;
3126
+ const symbol = type.members.find(
3127
+ (member) => member.kind === "enumMember" && member.name === reference2.name
3128
+ );
3129
+ return symbol ? { type: symbol.type, symbol } : null;
3130
+ }
2817
3131
  function switchCaseEnumContext(snapshot, offset) {
2818
3132
  for (const statement of snapshot.parsed.switches) {
2819
3133
  for (const section of statement.sections) {
@@ -3067,6 +3381,19 @@ function scopeAt(snapshot, offset) {
3067
3381
  )
3068
3382
  );
3069
3383
  }
3384
+ const declaringType = snapshot.context.declaringType;
3385
+ if (snapshot.context.implicitMemberAccess === true && declaringType?.kind === "named") {
3386
+ const owner = snapshot.project.typeById.get(declaringType.typeId);
3387
+ if (owner) {
3388
+ for (const member of owner.members) {
3389
+ if (snapshot.context.staticMember === true && member.static !== true) {
3390
+ continue;
3391
+ }
3392
+ if (!isMemberCompletionAccessible(snapshot, owner, member)) continue;
3393
+ result.push(withScope(member, 0, snapshot.source.text.length));
3394
+ }
3395
+ }
3396
+ }
3070
3397
  if (effectiveDocumentKind(snapshot, offset) === "setter" && snapshot.context.returnType) {
3071
3398
  result.push(
3072
3399
  withScope(
@@ -3903,6 +4230,11 @@ function isTypeAnnotationReference(snapshot, reference2) {
3903
4230
  (local) => local.typeTokens.some((token) => token.start === reference2.start)
3904
4231
  );
3905
4232
  }
4233
+ function isConstructorTypeReference(snapshot, reference2) {
4234
+ return snapshot.parsed.calls.some(
4235
+ (call) => call.kind === "constructor" && call.nameRange.start.line === reference2.range.start.line && call.nameRange.start.character === reference2.range.start.character
4236
+ );
4237
+ }
3906
4238
  var IDENTIFIER_PATTERN, RESERVED_NAMES;
3907
4239
  var init_analyzer = __esm({
3908
4240
  "../packages/neoscript-language/src/analyzer.ts"() {
@@ -21592,6 +21924,7 @@ function compileProjectSourceBodies(documents) {
21592
21924
  const graph = buildProjectGraph(documents);
21593
21925
  const projectIndex = createProjectIndex(graph.project);
21594
21926
  const bodies = [];
21927
+ const contexts = [];
21595
21928
  const diagnostics = [];
21596
21929
  for (const [uri, document] of documents) {
21597
21930
  if (document.kind !== "definition") continue;
@@ -21636,6 +21969,7 @@ function compileProjectSourceBodies(documents) {
21636
21969
  },
21637
21970
  projectIndex,
21638
21971
  bodies,
21972
+ contexts,
21639
21973
  diagnostics
21640
21974
  });
21641
21975
  }
@@ -21671,6 +22005,7 @@ function compileProjectSourceBodies(documents) {
21671
22005
  },
21672
22006
  projectIndex,
21673
22007
  bodies,
22008
+ contexts,
21674
22009
  diagnostics
21675
22010
  });
21676
22011
  }
@@ -21711,6 +22046,7 @@ function compileProjectSourceBodies(documents) {
21711
22046
  },
21712
22047
  projectIndex,
21713
22048
  bodies,
22049
+ contexts,
21714
22050
  diagnostics
21715
22051
  });
21716
22052
  continue;
@@ -21735,15 +22071,24 @@ function compileProjectSourceBodies(documents) {
21735
22071
  context: { ...baseContext2, kind: unit },
21736
22072
  projectIndex,
21737
22073
  bodies,
22074
+ contexts,
21738
22075
  diagnostics
21739
22076
  });
21740
22077
  }
21741
22078
  }
21742
22079
  }
21743
22080
  }
21744
- return { project: graph.project, bodies, diagnostics };
22081
+ return { project: graph.project, bodies, contexts, diagnostics };
21745
22082
  }
21746
22083
  function compileBody(args) {
22084
+ args.contexts.push({
22085
+ uri: args.uri,
22086
+ ownerName: args.owner.declaration.name,
22087
+ memberName: args.memberName,
22088
+ unit: args.unit,
22089
+ range: args.range,
22090
+ context: args.context
22091
+ });
21747
22092
  const source = new SourceText(args.document.sourceText);
21748
22093
  const start = source.offsetAt(args.range.start);
21749
22094
  const end = source.offsetAt(args.range.end);
@@ -24063,6 +24408,7 @@ function analyzeNeoProjectSources(inputs, parsedDocuments = /* @__PURE__ */ new
24063
24408
  symbols,
24064
24409
  project: bodyCompilation.project,
24065
24410
  compiledBodies: bodyCompilation.bodies,
24411
+ bodyContexts: bodyCompilation.contexts,
24066
24412
  diagnostics
24067
24413
  };
24068
24414
  }
@@ -24890,9 +25236,25 @@ var init_quick_fixes = __esm({
24890
25236
 
24891
25237
  // ../packages/neoscript-language/src/project-source-language-features.ts
24892
25238
  function projectDiagnostics(analysis, uri) {
24893
- return analysis.diagnostics.filter((diagnostic) => diagnostic.uri === uri).map(({ uri: _uri, ...diagnostic }) => diagnostic);
25239
+ return analysis.diagnostics.filter((diagnostic) => diagnostic.uri === uri).map((diagnostic) => ({
25240
+ range: diagnostic.range,
25241
+ severity: diagnostic.severity,
25242
+ message: diagnostic.message,
25243
+ ...diagnostic.code === void 0 ? {} : { code: diagnostic.code },
25244
+ ...diagnostic.source === void 0 ? {} : { source: diagnostic.source },
25245
+ ...diagnostic.relatedInformation === void 0 ? {} : { relatedInformation: diagnostic.relatedInformation },
25246
+ ...diagnostic.suggestions === void 0 ? {} : { suggestions: diagnostic.suggestions }
25247
+ }));
24894
25248
  }
24895
25249
  function projectCompletions(analysis, document, position) {
25250
+ const body = projectBodySnapshotAt(analysis, document, position);
25251
+ if (body) return complete(body, position);
25252
+ const annotations = projectAnnotationCompletions(
25253
+ analysis,
25254
+ document,
25255
+ position
25256
+ );
25257
+ if (annotations) return { isIncomplete: false, items: annotations };
24896
25258
  const members = projectMemberCompletions(analysis, document, position);
24897
25259
  if (members) {
24898
25260
  return {
@@ -24977,13 +25339,126 @@ function projectCompletions(analysis, document, position) {
24977
25339
  if (initializerMembers) {
24978
25340
  return { isIncomplete: false, items: initializerMembers };
24979
25341
  }
25342
+ const contextualEnum = projectContextualEnumCompletions(
25343
+ analysis,
25344
+ document,
25345
+ position
25346
+ );
25347
+ if (contextualEnum) {
25348
+ return { isIncomplete: false, items: contextualEnum };
25349
+ }
25350
+ return projectFallbackCompletions(analysis, document, position);
25351
+ }
25352
+ function projectAnnotationCompletions(analysis, document, position) {
25353
+ const source = new SourceText(document.text);
25354
+ const offset = source.offsetAt(position);
25355
+ const word = projectWordRange(document.text, offset);
25356
+ if (document.text[word.start - 1] !== "@") return null;
25357
+ if (initializerRootAt(analysis, document, position)) return [];
25358
+ const names = projectAnnotationNamesAt(analysis, document, position);
25359
+ return names.map((name) => ({
25360
+ label: `@${name}`,
25361
+ kind: "snippet",
25362
+ insertText: name,
25363
+ textEdit: {
25364
+ range: source.range(word.start, word.end),
25365
+ newText: name
25366
+ }
25367
+ }));
25368
+ }
25369
+ function projectAnnotationNamesAt(analysis, document, position) {
25370
+ const source = analysis.documents.get(document.uri);
25371
+ if (!source) return [];
25372
+ if (document.languageId === "neoflow") {
25373
+ return ["id", "primary", "incomplete"];
25374
+ }
25375
+ const offset = new SourceText(document.text).offsetAt(position);
25376
+ const followingDeclaration = /^\s*(class|interface|enum)\b([^{}]*)/.exec(
25377
+ document.text.slice(offset)
25378
+ );
25379
+ if (followingDeclaration?.[1] === "class") {
25380
+ return [
25381
+ "id",
25382
+ "hidden",
25383
+ "settings",
25384
+ "storage",
25385
+ "relations",
25386
+ .../\bDialogue\b/.test(followingDeclaration[2] ?? "") ? ["incomplete"] : []
25387
+ ];
25388
+ }
25389
+ if (followingDeclaration) return ["id"];
25390
+ const containing = declarationContaining(source, position);
25391
+ if (containing?.kind === "enum") return ["id"];
25392
+ if (containing?.kind === "class" || containing?.kind === "interface") {
25393
+ if (positionCompare(position, containing.nameRange.start) < 0) {
25394
+ return projectDeclarationAnnotationNames(containing);
25395
+ }
25396
+ const member = containing.members.find(
25397
+ (candidate) => rangeContains(candidate.range, position) || positionCompare(position, candidate.range.start) <= 0
25398
+ );
25399
+ return projectMemberAnnotationNames(member);
25400
+ }
25401
+ const following = source.declarations.find(
25402
+ (declaration) => positionCompare(position, declaration.range.start) <= 0
25403
+ );
25404
+ if (following) return projectDeclarationAnnotationNames(following);
25405
+ return ["id"];
25406
+ }
25407
+ function projectDeclarationAnnotationNames(declaration) {
25408
+ if (declaration.kind !== "class") return ["id"];
25409
+ return [
25410
+ "id",
25411
+ "hidden",
25412
+ "settings",
25413
+ "storage",
25414
+ "relations",
25415
+ ...declaration.baseTypes.some((type) => type.name === "Dialogue") ? ["incomplete"] : []
25416
+ ];
25417
+ }
25418
+ function projectMemberAnnotationNames(member) {
25419
+ const names = ["id", "settings", "storage", "locked"];
25420
+ if (member?.type.name === "List") names.push("index", "column");
25421
+ return names;
25422
+ }
25423
+ function projectContextualEnumCompletions(analysis, document, position) {
25424
+ const source = new SourceText(document.text);
25425
+ const offset = source.offsetAt(position);
25426
+ const word = projectWordRange(document.text, offset);
25427
+ if (!isContextualDot(document.text, word.start)) return null;
25428
+ const typeName = constructionSiteAt(
25429
+ analysis,
25430
+ document,
25431
+ position
25432
+ )?.expectedTypeName;
25433
+ if (!typeName) return null;
25434
+ const type = analysis.project.types.find(
25435
+ (candidate) => candidate.kind === "enum" && candidate.name === typeName
25436
+ );
25437
+ if (!type) return null;
25438
+ return type.members.filter((member) => member.kind === "enumMember").map((member) => ({
25439
+ label: `.${member.name}`,
25440
+ kind: "enumMember",
25441
+ detail: type.name,
25442
+ insertText: member.name,
25443
+ textEdit: {
25444
+ range: source.range(word.start, word.end),
25445
+ newText: member.name
25446
+ },
25447
+ symbolId: member.id
25448
+ }));
25449
+ }
25450
+ function projectFallbackCompletions(analysis, document, position) {
24980
25451
  const items = /* @__PURE__ */ new Map();
24981
- for (const keyword of [
24982
- "@id",
24983
- "@settings",
24984
- "@storage",
24985
- "@relations",
24986
- "@primary",
25452
+ const construction = constructionSiteAt(analysis, document, position);
25453
+ const recoveredInitializerType = projectInitializerExpectedTypeNameAt(
25454
+ document,
25455
+ position
25456
+ );
25457
+ const expression = construction !== null || recoveredInitializerType !== null;
25458
+ const expectedTypeName = construction?.expectedTypeName ?? recoveredInitializerType;
25459
+ const afterNew = expression && projectAfterNewAt(document, position);
25460
+ const typePosition = projectTypePositionAt(analysis, document, position);
25461
+ const keywords = expression ? afterNew ? [] : ["new"] : typePosition ? [] : [
24987
25462
  "class",
24988
25463
  "interface",
24989
25464
  "enum",
@@ -24993,16 +25468,20 @@ function projectCompletions(analysis, document, position) {
24993
25468
  "override",
24994
25469
  "readonly",
24995
25470
  "virtual",
24996
- "static",
24997
- "new"
24998
- ]) {
25471
+ "static"
25472
+ ];
25473
+ for (const keyword of keywords) {
24999
25474
  items.set(keyword, {
25000
25475
  label: keyword,
25001
- kind: keyword.startsWith("@") ? "snippet" : "keyword",
25476
+ kind: "keyword",
25002
25477
  insertText: keyword
25003
25478
  });
25004
25479
  }
25005
- for (const typeName of NEOSCRIPT_BUILTIN_TYPES) {
25480
+ for (const typeName of [
25481
+ ...NEOSCRIPT_PRIMITIVE_TYPES,
25482
+ ...NEOSCRIPT_BUILTIN_TYPES
25483
+ ]) {
25484
+ if (expression) continue;
25006
25485
  items.set(typeName, {
25007
25486
  label: typeName,
25008
25487
  kind: "class",
@@ -25010,10 +25489,31 @@ function projectCompletions(analysis, document, position) {
25010
25489
  insertText: typeName
25011
25490
  });
25012
25491
  }
25492
+ const source = analysis.documents.get(document.uri);
25493
+ const owner = source ? declarationContaining(source, position)?.name : void 0;
25013
25494
  for (const symbol of analysis.symbols) {
25014
25495
  if (symbol.scopeRange && (symbol.location.uri !== document.uri || !rangeContains(symbol.scopeRange, position))) {
25015
25496
  continue;
25016
25497
  }
25498
+ const isType = symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter";
25499
+ if (typePosition && !isType) continue;
25500
+ if (expression) {
25501
+ if (afterNew) {
25502
+ if (!isType || symbol.kind !== "class" || expectedTypeName !== null && !projectTypeNameAssignable(analysis, symbol.name, expectedTypeName)) {
25503
+ continue;
25504
+ }
25505
+ } else {
25506
+ const isValue = symbol.kind === "parameter" || symbol.kind === "flowBinding" || symbol.kind === "graphChild" || symbol.kind === "global" || symbol.kind === "member" && symbol.ownerName === owner;
25507
+ if (!isValue) continue;
25508
+ if (expectedTypeName !== null && (symbol.detail === void 0 || !projectTypeNameAssignable(
25509
+ analysis,
25510
+ symbol.detail,
25511
+ expectedTypeName
25512
+ ))) {
25513
+ continue;
25514
+ }
25515
+ }
25516
+ }
25017
25517
  const kind = projectCompletionKind(symbol);
25018
25518
  items.set(symbol.name, {
25019
25519
  label: symbol.name,
@@ -25025,6 +25525,168 @@ function projectCompletions(analysis, document, position) {
25025
25525
  }
25026
25526
  return { isIncomplete: false, items: [...items.values()] };
25027
25527
  }
25528
+ function projectInitializerExpectedTypeNameAt(document, position) {
25529
+ const prefix = documentPrefix(document.text, position);
25530
+ const line = prefix.slice(prefix.lastIndexOf("\n") + 1);
25531
+ return /^(?:\s*@\w+(?:\([^)]*\))?)*\s*(?:(?:public|protected|private|abstract|async|native|override|readonly|sealed|static|virtual)\s+)*([A-Za-z_][A-Za-z0-9_]*)(?:\s*<[^>]*>)?\s+[A-Za-z_][A-Za-z0-9_]*\s*=\s*[^;]*$/.exec(
25532
+ line
25533
+ )?.[1] ?? null;
25534
+ }
25535
+ function projectAfterNewAt(document, position) {
25536
+ const tokens = tokensBeforePosition(document.text, position);
25537
+ let cursor = tokens.length - 1;
25538
+ const current = tokens[cursor];
25539
+ const offset = new SourceText(document.text).offsetAt(position);
25540
+ if (current?.kind === "identifier" && current.text !== "new" && /[A-Za-z0-9_]$/.test(document.text.slice(0, offset))) {
25541
+ cursor--;
25542
+ }
25543
+ return tokens[cursor]?.text === "new";
25544
+ }
25545
+ function projectTypeNameAssignable(analysis, sourceName, targetName) {
25546
+ if (sourceName === targetName) return true;
25547
+ if (sourceName === "int" && (targetName === "float" || targetName === "decimal")) {
25548
+ return true;
25549
+ }
25550
+ const source = analysis.project.types.find(
25551
+ (type) => type.name === sourceName
25552
+ );
25553
+ const target = analysis.project.types.find(
25554
+ (type) => type.name === targetName
25555
+ );
25556
+ if (!source || !target) return false;
25557
+ const visited = /* @__PURE__ */ new Set();
25558
+ const derivesFrom = (typeId) => {
25559
+ if (typeId === target.id) return true;
25560
+ if (visited.has(typeId)) return false;
25561
+ visited.add(typeId);
25562
+ const type = analysis.project.types.find(
25563
+ (candidate) => candidate.id === typeId
25564
+ );
25565
+ return [
25566
+ ...type?.baseTypeIds ?? [],
25567
+ ...type?.interfaceTypeIds ?? []
25568
+ ].some(derivesFrom);
25569
+ };
25570
+ return derivesFrom(source.id);
25571
+ }
25572
+ function projectBodySnapshotAt(analysis, document, position) {
25573
+ const body = analysis.bodyContexts.find(
25574
+ (candidate) => candidate.uri === document.uri && rangeContains(candidate.range, position)
25575
+ );
25576
+ return body ? projectBodySnapshot(analysis, document, body) : null;
25577
+ }
25578
+ function projectBodySnapshot(analysis, document, body) {
25579
+ let snapshots = PROJECT_BODY_SNAPSHOT_CACHE.get(analysis);
25580
+ if (!snapshots) {
25581
+ snapshots = /* @__PURE__ */ new Map();
25582
+ PROJECT_BODY_SNAPSHOT_CACHE.set(analysis, snapshots);
25583
+ }
25584
+ const cached = snapshots.get(body);
25585
+ if (cached) return cached;
25586
+ const source = new SourceText(document.text);
25587
+ const start = source.offsetAt(body.range.start);
25588
+ const end = source.offsetAt(body.range.end);
25589
+ const text = maskOutsideProjectBody(document.text, start, end);
25590
+ const syntax = analyzeNeoScriptSyntax(text, body.context.kind);
25591
+ const context = { ...body.context, project: analysis.project };
25592
+ const snapshot = {
25593
+ uri: document.uri,
25594
+ source: syntax.lexed.source,
25595
+ lexed: syntax.lexed,
25596
+ parsed: syntax.parsed,
25597
+ context,
25598
+ project: buildProjectIndexWithBuiltins(context)
25599
+ };
25600
+ snapshots.set(body, snapshot);
25601
+ return snapshot;
25602
+ }
25603
+ function maskOutsideProjectBody(text, start, end) {
25604
+ let masked = "";
25605
+ for (let index = 0; index < text.length; index++) {
25606
+ const character = text[index] ?? "";
25607
+ masked += index >= start && index < end || character === "\n" || character === "\r" ? character : " ";
25608
+ }
25609
+ return masked;
25610
+ }
25611
+ function projectWordRange(text, offset) {
25612
+ let start = offset;
25613
+ let end = offset;
25614
+ while (start > 0 && /[A-Za-z0-9_]/.test(text[start - 1] ?? "")) start--;
25615
+ while (end < text.length && /[A-Za-z0-9_]/.test(text[end] ?? "")) end++;
25616
+ return { start, end };
25617
+ }
25618
+ function isContextualDot(text, wordStart) {
25619
+ if (text[wordStart - 1] !== ".") return false;
25620
+ let cursor = wordStart - 2;
25621
+ while (cursor >= 0 && /\s/.test(text[cursor] ?? "")) cursor--;
25622
+ const beforeDot = text[cursor];
25623
+ if (beforeDot !== void 0 && /[A-Za-z0-9_]/.test(beforeDot)) {
25624
+ const precedingWord = /[A-Za-z_][A-Za-z0-9_]*$/.exec(
25625
+ text.slice(0, cursor + 1)
25626
+ )?.[0];
25627
+ return precedingWord === "return" || precedingWord === "case";
25628
+ }
25629
+ return beforeDot === void 0 || !/[A-Za-z0-9_\])}]/.test(beforeDot);
25630
+ }
25631
+ function projectTypePositionAt(analysis, document, position) {
25632
+ const source = analysis.documents.get(document.uri);
25633
+ if (source && sourceTypeAt(source, position)) return true;
25634
+ const prefix = documentPrefix(document.text, position);
25635
+ const line = prefix.slice(prefix.lastIndexOf("\n") + 1);
25636
+ return /(?:^|[{:;,])\s*(?:(?:public|protected|private|abstract|async|native|override|readonly|sealed|static|virtual)\s+)*[A-Za-z_][A-Za-z0-9_]*(?:\s*<[^;={}()]*)?$/.test(
25637
+ line
25638
+ );
25639
+ }
25640
+ function sourceTypeAt(document, position) {
25641
+ const contains2 = (type) => {
25642
+ for (const argument2 of type.typeArguments) {
25643
+ const nested = contains2(argument2);
25644
+ if (nested) return nested;
25645
+ }
25646
+ return rangeContains(type.range, position) ? type : null;
25647
+ };
25648
+ const parameterType = (parameters) => {
25649
+ for (const parameter4 of parameters ?? []) {
25650
+ const match = contains2(parameter4.type);
25651
+ if (match) return match;
25652
+ }
25653
+ return null;
25654
+ };
25655
+ for (const declaration of document.declarations) {
25656
+ if (declaration.kind === "global") {
25657
+ const match = contains2(declaration.type);
25658
+ if (match) return match;
25659
+ continue;
25660
+ }
25661
+ if (declaration.kind === "enum") continue;
25662
+ for (const base of declaration.baseTypes) {
25663
+ const match = contains2(base);
25664
+ if (match) return match;
25665
+ }
25666
+ if (declaration.kind === "class") {
25667
+ const header = parameterType(declaration.headerParameters);
25668
+ if (header) return header;
25669
+ for (const generic of declaration.genericParameters) {
25670
+ if (!generic.constraint) continue;
25671
+ const match = contains2(generic.constraint);
25672
+ if (match) return match;
25673
+ }
25674
+ for (const constructor2 of declaration.constructors) {
25675
+ const match = parameterType(constructor2.parameters);
25676
+ if (match) return match;
25677
+ }
25678
+ }
25679
+ for (const member of declaration.members) {
25680
+ const memberType2 = contains2(member.type);
25681
+ if (memberType2) return memberType2;
25682
+ if (member.kind === "function") {
25683
+ const match = parameterType(member.parameters);
25684
+ if (match) return match;
25685
+ }
25686
+ }
25687
+ }
25688
+ return null;
25689
+ }
25028
25690
  function projectMemberCompletions(analysis, document, position) {
25029
25691
  const tokens = projectTokens(document).filter(
25030
25692
  (token) => token.kind !== "eof" && token.kind !== "comment" && positionCompare(token.range.start, position) <= 0
@@ -25262,6 +25924,8 @@ function activeNamedArgument(text, position) {
25262
25924
  return /([A-Za-z_][A-Za-z0-9_]*)\s*:\s*\.[A-Za-z0-9_]*$/.exec(prefix)?.[1] ?? null;
25263
25925
  }
25264
25926
  function projectHover(analysis, document, position) {
25927
+ const body = projectBodySnapshotAt(analysis, document, position);
25928
+ if (body) return hover(body, position);
25265
25929
  const resolved = projectSymbolAt(analysis, document, position);
25266
25930
  if (!resolved) return null;
25267
25931
  const owner = resolved.symbol.ownerName ? ` on \`${resolved.symbol.ownerName}\`` : "";
@@ -25309,6 +25973,8 @@ Construct with \`new(${parameters})\` \u2014 this class declares a required cons
25309
25973
  ${lines.join("\n\n")}`;
25310
25974
  }
25311
25975
  function projectDefinition(analysis, document, position) {
25976
+ const body = projectBodySnapshotAt(analysis, document, position);
25977
+ if (body) return definition(body, position);
25312
25978
  const resolved = projectSymbolAt(analysis, document, position);
25313
25979
  return resolved ? [resolved.symbol.location] : [];
25314
25980
  }
@@ -25375,6 +26041,8 @@ function requiredDestinationEdit(document, initializer, destination) {
25375
26041
  return open && close ? trailingBlockReturnEdit(document.text, open, close, destination) : null;
25376
26042
  }
25377
26043
  function projectSignatureHelp(analysis, document, position) {
26044
+ const body = projectBodySnapshotAt(analysis, document, position);
26045
+ if (body) return signatureHelp(body, position);
25378
26046
  const tokens = tokensBeforePosition(document.text, position);
25379
26047
  const openIndex = activeCallOpenIndex(tokens, position);
25380
26048
  if (openIndex < 0) return null;
@@ -25436,6 +26104,9 @@ function projectSignatureHelp(analysis, document, position) {
25436
26104
  };
25437
26105
  }
25438
26106
  function projectInlayHints(analysis, document, range2) {
26107
+ const bodyContexts = analysis.bodyContexts.filter(
26108
+ (body) => body.uri === document.uri
26109
+ );
25439
26110
  const tokens = lex(document.text).tokens.filter(
25440
26111
  (token) => token.kind !== "comment" && token.kind !== "eof"
25441
26112
  );
@@ -25450,6 +26121,9 @@ function projectInlayHints(analysis, document, range2) {
25450
26121
  if (!callee || callee.kind !== "identifier" && callee.kind !== "type") {
25451
26122
  continue;
25452
26123
  }
26124
+ if (bodyContexts.some((body) => rangeContains(body.range, callee.range.start))) {
26125
+ continue;
26126
+ }
25453
26127
  if (!open || open.text !== "(") continue;
25454
26128
  const beforeCallee = tokens[index - 1];
25455
26129
  if (beforeCallee?.text === "@") continue;
@@ -25476,6 +26150,14 @@ function projectInlayHints(analysis, document, range2) {
25476
26150
  });
25477
26151
  }
25478
26152
  }
26153
+ for (const body of bodyContexts) {
26154
+ const snapshot = projectBodySnapshot(analysis, document, body);
26155
+ hints.push(
26156
+ ...inlayHints(snapshot, range2).filter(
26157
+ (hint) => rangeContains(body.range, hint.position)
26158
+ )
26159
+ );
26160
+ }
25479
26161
  return hints;
25480
26162
  }
25481
26163
  function projectCallParameterNames(analysis, document, callee, beforeCallee) {
@@ -25677,8 +26359,14 @@ function symbolsDeclaredAt(index, uri, range2) {
25677
26359
  function projectSemanticTokens(document, analysis) {
25678
26360
  const index = buildProjectSymbolIndex(analysis.symbols);
25679
26361
  const tokens = projectTokens(document);
25680
- return tokens.flatMap((token, tokenIndex) => {
26362
+ const bodyContexts = analysis.bodyContexts.filter(
26363
+ (body) => body.uri === document.uri
26364
+ );
26365
+ const result = tokens.flatMap((token, tokenIndex) => {
25681
26366
  if (token.kind === "eof" || token.kind === "error") return [];
26367
+ if (bodyContexts.some((body) => rangeContains(body.range, token.range.start))) {
26368
+ return [];
26369
+ }
25682
26370
  const declaration = symbolsDeclaredAt(index, document.uri, token.range)[0];
25683
26371
  const resolved = token.kind === "identifier" ? indexedProjectSymbolAt(analysis, document, token.range.start, index)?.symbol : void 0;
25684
26372
  let type;
@@ -25705,6 +26393,17 @@ function projectSemanticTokens(document, analysis) {
25705
26393
  }
25706
26394
  ];
25707
26395
  });
26396
+ for (const body of bodyContexts) {
26397
+ const snapshot = projectBodySnapshot(analysis, document, body);
26398
+ result.push(
26399
+ ...semanticTokens(snapshot).filter(
26400
+ (token) => rangeContains(body.range, token.range.start)
26401
+ )
26402
+ );
26403
+ }
26404
+ return result.sort(
26405
+ (left, right) => positionCompare(left.range.start, right.range.start)
26406
+ );
25708
26407
  }
25709
26408
  function isProjectContextualKeyword(tokens, tokenIndex) {
25710
26409
  const token = tokens[tokenIndex];
@@ -25729,6 +26428,17 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
25729
26428
  if (exact.length === 1) return { token, symbol: exact[0] };
25730
26429
  const candidates = index.byName.get(token.text) ?? [];
25731
26430
  if (candidates.length === 0) return null;
26431
+ const sourceTokens = projectTokens(document);
26432
+ const tokenIndex = sourceTokens.findIndex(
26433
+ (candidate) => candidate.start === token.start && candidate.end === token.end
26434
+ );
26435
+ const source = analysis.documents.get(document.uri);
26436
+ if (source && sourceTypeAt(source, token.range.start) || sourceTokens[tokenIndex - 1]?.text === "new") {
26437
+ const types = candidates.filter(
26438
+ (symbol) => symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter"
26439
+ );
26440
+ if (types.length === 1) return { token, symbol: types[0] };
26441
+ }
25732
26442
  const scoped = candidates.filter(
25733
26443
  (symbol) => symbol.scopeRange && symbol.location.uri === document.uri && rangeContains(symbol.scopeRange, token.range.start)
25734
26444
  ).sort(
@@ -25740,10 +26450,6 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
25740
26450
  return { token, symbol: visibleCandidates[0] };
25741
26451
  }
25742
26452
  if (visibleCandidates.length === 0) return null;
25743
- const sourceTokens = projectTokens(document);
25744
- const tokenIndex = sourceTokens.findIndex(
25745
- (candidate) => candidate.start === token.start && candidate.end === token.end
25746
- );
25747
26453
  if (sourceTokens[tokenIndex + 1]?.text === "=" && sourceTokens[tokenIndex + 2]?.text !== "=") {
25748
26454
  const constructed = constructionMemberSymbol(
25749
26455
  analysis,
@@ -25753,7 +26459,6 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
25753
26459
  );
25754
26460
  if (constructed) return { token, symbol: constructed };
25755
26461
  }
25756
- const source = analysis.documents.get(document.uri);
25757
26462
  const owner = source ? declarationContaining(source, token.range.start)?.name : void 0;
25758
26463
  if (owner) {
25759
26464
  const owned = visibleCandidates.filter(
@@ -26272,10 +26977,11 @@ function projectFunctionsNamed(analysis, name) {
26272
26977
  }
26273
26978
  return result;
26274
26979
  }
26275
- var CONSTRUCTION_INDEX_CACHE;
26980
+ var PROJECT_BODY_SNAPSHOT_CACHE, CONSTRUCTION_INDEX_CACHE;
26276
26981
  var init_project_source_language_features = __esm({
26277
26982
  "../packages/neoscript-language/src/project-source-language-features.ts"() {
26278
26983
  "use strict";
26984
+ init_analyzer();
26279
26985
  init_lexer();
26280
26986
  init_language_spec();
26281
26987
  init_project_source_semantics();
@@ -26283,6 +26989,8 @@ var init_project_source_language_features = __esm({
26283
26989
  init_project_source_tokens();
26284
26990
  init_quick_fixes();
26285
26991
  init_source_text();
26992
+ init_syntax();
26993
+ PROJECT_BODY_SNAPSHOT_CACHE = /* @__PURE__ */ new WeakMap();
26286
26994
  CONSTRUCTION_INDEX_CACHE = /* @__PURE__ */ new WeakMap();
26287
26995
  }
26288
26996
  });
@@ -45984,9 +46692,9 @@ ${namedArguments(
45984
46692
  `;
45985
46693
  }
45986
46694
  function namedArguments(entries, spaces, raw = /* @__PURE__ */ new Set()) {
45987
- const pad = " ".repeat(spaces);
46695
+ const pad2 = " ".repeat(spaces);
45988
46696
  return entries.map(
45989
- ([name, value]) => `${pad}${name}: ${raw.has(name) ? rawValue(value) : jsonValue2(value)},`
46697
+ ([name, value]) => `${pad2}${name}: ${raw.has(name) ? rawValue(value) : jsonValue2(value)},`
45990
46698
  ).join("\n");
45991
46699
  }
45992
46700
  function rawValue(value) {
@@ -50456,15 +51164,14 @@ function commentEndIndex(source, index) {
50456
51164
  return null;
50457
51165
  }
50458
51166
  function stripInitializerComments(source) {
50459
- const lines = [{ text: "", removedComment: false }];
51167
+ const lines = [{ parts: [], removedComment: false }];
50460
51168
  let quote6 = null;
50461
51169
  let escaped = false;
50462
51170
  let index = 0;
51171
+ let segmentStart = 0;
50463
51172
  while (index < source.length) {
50464
51173
  const character = source[index];
50465
- const line = lines[lines.length - 1];
50466
51174
  if (quote6 !== null) {
50467
- line.text += character;
50468
51175
  if (escaped) escaped = false;
50469
51176
  else if (character === "\\") escaped = true;
50470
51177
  else if (character === quote6) quote6 = null;
@@ -50473,20 +51180,34 @@ function stripInitializerComments(source) {
50473
51180
  }
50474
51181
  const afterComment = commentEndIndex(source, index);
50475
51182
  if (afterComment !== null) {
51183
+ const line = lines[lines.length - 1];
51184
+ if (segmentStart < index) {
51185
+ line.parts.push(source.slice(segmentStart, index));
51186
+ }
50476
51187
  line.removedComment = true;
50477
51188
  index = afterComment;
51189
+ segmentStart = index;
50478
51190
  continue;
50479
51191
  }
50480
51192
  if (character === "\n") {
50481
- lines.push({ text: "", removedComment: false });
51193
+ if (segmentStart < index) {
51194
+ lines[lines.length - 1].parts.push(source.slice(segmentStart, index));
51195
+ }
51196
+ lines.push({ parts: [], removedComment: false });
50482
51197
  index += 1;
51198
+ segmentStart = index;
50483
51199
  continue;
50484
51200
  }
50485
51201
  if (character === '"' || character === "'") quote6 = character;
50486
- line.text += character;
50487
51202
  index += 1;
50488
51203
  }
50489
- return lines.filter((line) => !line.removedComment || line.text.trim().length > 0).map((line) => line.text.trimEnd()).join("\n").trim();
51204
+ if (segmentStart < source.length) {
51205
+ lines[lines.length - 1].parts.push(source.slice(segmentStart));
51206
+ }
51207
+ return lines.map((line) => ({
51208
+ text: line.parts.join("").trimEnd(),
51209
+ removedComment: line.removedComment
51210
+ })).filter((line) => !line.removedComment || line.text.trim().length > 0).map((line) => line.text).join("\n").trim();
50490
51211
  }
50491
51212
  function normalizeBodySource(source) {
50492
51213
  const trimmed = source.trim();
@@ -56658,6 +57379,19 @@ var init_NeoScriptScope = __esm({
56658
57379
  setLocal(bindingId, value) {
56659
57380
  this.#bindings.set(bindingId, value);
56660
57381
  }
57382
+ bindInvocationEntry(bindingId, value) {
57383
+ this.#bindings.set(bindingId, value);
57384
+ }
57385
+ bindInvocationKeyAndEntry(keyBindingId, key, entryBindingId, entry) {
57386
+ this.#bindings.set(keyBindingId, key);
57387
+ this.#bindings.set(entryBindingId, entry);
57388
+ }
57389
+ resetInvocationLocals(parameterCount) {
57390
+ if (this.#bindings.size > parameterCount) this.#bindings.clear();
57391
+ if (this.#readonlyBindingErrors.size > 0) {
57392
+ this.#readonlyBindingErrors.clear();
57393
+ }
57394
+ }
56661
57395
  *keys() {
56662
57396
  const inherited = /* @__PURE__ */ new Set();
56663
57397
  if (this.parent !== null) {
@@ -61699,6 +62433,211 @@ function evalDeclaredListIndex(info, scope, ctx) {
61699
62433
  }
61700
62434
  return Array.isArray(hit) ? [...hit] : [];
61701
62435
  }
62436
+ function prepareCollectionCallback(callback, parentScope, ctx, isList, returnContract, onPredicateMatch) {
62437
+ const metrics = ctx.__collectionCallbackPreparationMetrics;
62438
+ if (metrics !== void 0) metrics.bodyValidations += 1;
62439
+ const compilerRevision = callback.compilerRevision ?? 1;
62440
+ if (!Number.isSafeInteger(compilerRevision)) {
62441
+ throw new NSGetterRuntimeError(
62442
+ "Collection callback compiler revision must be a safe integer."
62443
+ );
62444
+ }
62445
+ if (compilerRevision < 1) {
62446
+ throw new NSGetterRuntimeError(
62447
+ "Collection callback compiler revision must be at least 1."
62448
+ );
62449
+ }
62450
+ if (compilerRevision > NEOSCRIPT_COMPILER_REVISION) {
62451
+ throw new NSGetterRuntimeError(
62452
+ `Collection callback compiler revision ${String(compilerRevision)} is newer than supported revision ${String(NEOSCRIPT_COMPILER_REVISION)}.`
62453
+ );
62454
+ }
62455
+ if (!isNSFunctionWithReturnType(callback)) {
62456
+ throw new NSGetterRuntimeError(
62457
+ `Collection callback body metadata is invalid for compiler revision ${String(compilerRevision)}.`
62458
+ );
62459
+ }
62460
+ const parameters = callback.parameters;
62461
+ if (parameters.length < 1) {
62462
+ throw new NSGetterRuntimeError(
62463
+ "Collection callback requires at least one parameter."
62464
+ );
62465
+ }
62466
+ if (parameters.length > 2) {
62467
+ throw new NSGetterRuntimeError(
62468
+ `Collection callback supports at most two parameters, but received ${String(parameters.length)}.`
62469
+ );
62470
+ }
62471
+ if (returnContract === "predicate") {
62472
+ if (callback.typeInfo.type !== 1 /* Bool */) {
62473
+ throw new NSGetterRuntimeError(
62474
+ "Collection predicate callback must declare a Bool return type."
62475
+ );
62476
+ }
62477
+ if (!callback.typeInfo.required) {
62478
+ throw new NSGetterRuntimeError(
62479
+ "Collection predicate callback must declare a required return type."
62480
+ );
62481
+ }
62482
+ }
62483
+ const parameterCount = parameters.length;
62484
+ const callbackScope = ctx.__collectionCallbackStrategy === "fresh" ? null : createChildScope(parentScope);
62485
+ const callbackOptions = evaluationOptions(ctx, false);
62486
+ const instructions = callback.instructions;
62487
+ const returnTypeInfo = callback.typeInfo;
62488
+ const requiresInvocationReset = callbackBodyMayAddInvocationLocals(
62489
+ instructions,
62490
+ new Set(parameters.map((parameter4) => parameter4.id))
62491
+ );
62492
+ let finishBody;
62493
+ if (returnContract === "predicate") {
62494
+ finishBody = (innerScope2) => {
62495
+ const result = evalInstructions(
62496
+ instructions,
62497
+ innerScope2,
62498
+ ctx,
62499
+ callbackOptions
62500
+ );
62501
+ rejectEscapedLoopTransfer(result, "collection callback");
62502
+ if (result.kind !== "return") {
62503
+ throw new NSGetterRuntimeError(
62504
+ "Collection callback ended without returning a value."
62505
+ );
62506
+ }
62507
+ const value = result.value;
62508
+ if (typeof value !== "boolean") {
62509
+ throw new NSGetterRuntimeError(
62510
+ "Collection predicate callback returned a value that does not match its required Bool contract."
62511
+ );
62512
+ }
62513
+ return value;
62514
+ };
62515
+ } else {
62516
+ finishBody = (innerScope2) => {
62517
+ const result = evalInstructions(
62518
+ instructions,
62519
+ innerScope2,
62520
+ ctx,
62521
+ callbackOptions
62522
+ );
62523
+ rejectEscapedLoopTransfer(result, "collection callback");
62524
+ if (result.kind !== "return") {
62525
+ throw new NSGetterRuntimeError(
62526
+ "Collection callback ended without returning a value."
62527
+ );
62528
+ }
62529
+ let value = result.value;
62530
+ if (returnTypeInfo.type === 20 /* Decimal */ && typeof value === "number") {
62531
+ value = coerceDecimalOperand(value, "collection callback return");
62532
+ }
62533
+ if (!runtimeValueMatchesType(value, returnTypeInfo, ctx)) {
62534
+ throw new NSGetterRuntimeError(
62535
+ "Collection projection callback returned a value that does not match its compiled return type."
62536
+ );
62537
+ }
62538
+ return value;
62539
+ };
62540
+ }
62541
+ if (metrics !== void 0) metrics.bindingPlanCreations += 1;
62542
+ if (ctx.__collectionCallbackStrategy === "fresh" || ctx.__collectionCallbackStrategy === "prepared") {
62543
+ return (entry, key, valueId) => {
62544
+ if (onPredicateMatch !== void 0) {
62545
+ consumeBudget(ctx, "workUnits", 1, "work unit");
62546
+ }
62547
+ const innerScope2 = callbackScope ?? createChildScope(parentScope);
62548
+ if (callbackScope !== null) {
62549
+ callbackScope.resetInvocationLocals(parameterCount);
62550
+ }
62551
+ if (parameters.length === 1) {
62552
+ innerScope2.setLocal(parameters[0].id, entry);
62553
+ } else if (parameters.length === 2) {
62554
+ innerScope2.setLocal(
62555
+ parameters[0].id,
62556
+ isList ? Number(key) : String(key)
62557
+ );
62558
+ innerScope2.setLocal(parameters[1].id, entry);
62559
+ }
62560
+ const value = finishBody(innerScope2);
62561
+ if (onPredicateMatch === void 0) return value;
62562
+ return value === true ? onPredicateMatch(entry, key, valueId) : 0 /* Continue */;
62563
+ };
62564
+ }
62565
+ const innerScope = callbackScope ?? createChildScope(parentScope);
62566
+ let bindValues;
62567
+ if (parameterCount === 1) {
62568
+ const entryParameterId = parameters[0].id;
62569
+ bindValues = (entry) => {
62570
+ innerScope.bindInvocationEntry(entryParameterId, entry);
62571
+ };
62572
+ } else {
62573
+ const keyParameterId = parameters[0].id;
62574
+ const entryParameterId = parameters[1].id;
62575
+ bindValues = (entry, key) => {
62576
+ innerScope.bindInvocationKeyAndEntry(
62577
+ keyParameterId,
62578
+ key,
62579
+ entryParameterId,
62580
+ entry
62581
+ );
62582
+ };
62583
+ }
62584
+ if (returnContract === "predicate") {
62585
+ if (onPredicateMatch === void 0) {
62586
+ throw new NSGetterRuntimeError(
62587
+ "Collection predicate preparation requires a match handler."
62588
+ );
62589
+ }
62590
+ if (requiresInvocationReset) {
62591
+ return (entry, key, valueId) => {
62592
+ consumeBudget(ctx, "workUnits", 1, "work unit");
62593
+ innerScope.resetInvocationLocals(parameterCount);
62594
+ bindValues(entry, key);
62595
+ return finishBody(innerScope) === true ? onPredicateMatch(entry, key, valueId) : 0 /* Continue */;
62596
+ };
62597
+ }
62598
+ return (entry, key, valueId) => {
62599
+ consumeBudget(ctx, "workUnits", 1, "work unit");
62600
+ bindValues(entry, key);
62601
+ return finishBody(innerScope) === true ? onPredicateMatch(entry, key, valueId) : 0 /* Continue */;
62602
+ };
62603
+ }
62604
+ if (requiresInvocationReset) {
62605
+ return (entry, key) => {
62606
+ innerScope.resetInvocationLocals(parameterCount);
62607
+ bindValues(entry, key);
62608
+ return finishBody(innerScope);
62609
+ };
62610
+ }
62611
+ return (entry, key) => {
62612
+ bindValues(entry, key);
62613
+ return finishBody(innerScope);
62614
+ };
62615
+ }
62616
+ function callbackBodyMayAddInvocationLocals(instructions, parameterIds) {
62617
+ return instructions.some((instruction) => {
62618
+ switch (instruction.type) {
62619
+ case "variable" /* variable */:
62620
+ return !parameterIds.has(instruction.variable.id);
62621
+ case "assign" /* assign */:
62622
+ return instruction.target.pointer.type === "variable" /* variable */ && !parameterIds.has(instruction.target.pointer.variableId);
62623
+ case "if" /* if */:
62624
+ case "for" /* for */:
62625
+ case "forEach" /* forEach */:
62626
+ case "switch" /* switch */:
62627
+ case "try" /* try */:
62628
+ return true;
62629
+ default:
62630
+ return false;
62631
+ }
62632
+ });
62633
+ }
62634
+ function isListCollection(collection) {
62635
+ if (Array.isArray(collection)) return true;
62636
+ if (typeof collection === "object" && collection !== null) return false;
62637
+ throw new NSGetterRuntimeError(
62638
+ "Collection callback receiver must be a present List or Dictionary value."
62639
+ );
62640
+ }
61702
62641
  function evalFunction(fn, scope, ctx) {
61703
62642
  switch (fn.type) {
61704
62643
  case "classConstructor" /* classConstructor */:
@@ -61885,27 +62824,15 @@ function evalFunction(fn, scope, ctx) {
61885
62824
  case "where" /* where */: {
61886
62825
  const c = evalPointer(fn.info.collectionPointer, scope, ctx);
61887
62826
  const innerFn = fn.info.function;
61888
- const isList = Array.isArray(c);
62827
+ const isList = isListCollection(c);
61889
62828
  const out = isList ? [] : {};
61890
- iterateCollection(c, ctx, (entry, key, valueId) => {
61891
- consumeBudget(ctx, "workUnits", 1, "work unit");
61892
- const innerScope = pushParams(
61893
- scope,
61894
- innerFn.parameters,
61895
- [
61896
- isList ? key : key,
61897
- // first param: index for list, key for dict
61898
- entry
61899
- ],
61900
- isList
61901
- );
61902
- const result = evalInstructions(
61903
- innerFn.instructions,
61904
- innerScope,
61905
- ctx,
61906
- evaluationOptions(ctx, false)
61907
- );
61908
- if (result.kind === "return" && result.value === true) {
62829
+ const callback = prepareCollectionCallback(
62830
+ innerFn,
62831
+ scope,
62832
+ ctx,
62833
+ isList,
62834
+ "predicate",
62835
+ (entry, key, valueId) => {
61909
62836
  consumeBudget(
61910
62837
  ctx,
61911
62838
  "producedCollectionEntries",
@@ -61917,42 +62844,38 @@ function evalFunction(fn, scope, ctx) {
61917
62844
  } else {
61918
62845
  out[String(key)] = valueId ?? entry;
61919
62846
  }
62847
+ return 0 /* Continue */;
61920
62848
  }
61921
- return 0 /* Continue */;
61922
- });
62849
+ );
62850
+ iterateCollection(c, ctx, callback);
61923
62851
  return out;
61924
62852
  }
61925
62853
  case "first" /* first */:
61926
62854
  case "firstOrDefault" /* firstOrDefault */: {
61927
62855
  const c = evalPointer(fn.info.collectionPointer, scope, ctx);
61928
62856
  const innerFn = fn.info.function ?? null;
61929
- const isList = Array.isArray(c);
62857
+ const isList = isListCollection(c);
61930
62858
  const sentinel = /* @__PURE__ */ Symbol("not-found");
61931
62859
  let found = sentinel;
61932
- iterateCollection(c, ctx, (entry, key) => {
61933
- if (!innerFn) {
62860
+ if (innerFn === null) {
62861
+ iterateCollection(c, ctx, (entry) => {
61934
62862
  found = entry;
61935
62863
  return 1 /* Break */;
61936
- }
61937
- consumeBudget(ctx, "workUnits", 1, "work unit");
61938
- const innerScope = pushParams(
62864
+ });
62865
+ } else {
62866
+ const callback = prepareCollectionCallback(
62867
+ innerFn,
61939
62868
  scope,
61940
- innerFn.parameters,
61941
- [key, entry],
61942
- isList
61943
- );
61944
- const result = evalInstructions(
61945
- innerFn.instructions,
61946
- innerScope,
61947
62869
  ctx,
61948
- evaluationOptions(ctx, false)
62870
+ isList,
62871
+ "predicate",
62872
+ (entry) => {
62873
+ found = entry;
62874
+ return 1 /* Break */;
62875
+ }
61949
62876
  );
61950
- if (result.kind === "return" && result.value === true) {
61951
- found = entry;
61952
- return 1 /* Break */;
61953
- }
61954
- return 0 /* Continue */;
61955
- });
62877
+ iterateCollection(c, ctx, callback);
62878
+ }
61956
62879
  if (found !== sentinel) return found;
61957
62880
  if (fn.type === "first" /* first */) {
61958
62881
  throw new NSGetterRuntimeError(
@@ -61964,31 +62887,25 @@ function evalFunction(fn, scope, ctx) {
61964
62887
  case "select" /* select */: {
61965
62888
  const c = evalPointer(fn.info.collectionPointer, scope, ctx);
61966
62889
  const innerFn = fn.info.function;
61967
- const isList = Array.isArray(c);
62890
+ const isList = isListCollection(c);
62891
+ const callback = prepareCollectionCallback(
62892
+ innerFn,
62893
+ scope,
62894
+ ctx,
62895
+ isList,
62896
+ "projection"
62897
+ );
61968
62898
  const out = [];
61969
- iterateCollection(c, ctx, (entry, key) => {
62899
+ iterateCollection(c, ctx, (entry, key, valueId) => {
61970
62900
  consumeBudget(ctx, "workUnits", 1, "work unit");
61971
- const innerScope = pushParams(
61972
- scope,
61973
- innerFn.parameters,
61974
- [key, entry],
61975
- isList
61976
- );
61977
- const result = evalInstructions(
61978
- innerFn.instructions,
61979
- innerScope,
62901
+ const value = callback(entry, key, valueId);
62902
+ consumeBudget(
61980
62903
  ctx,
61981
- evaluationOptions(ctx, false)
62904
+ "producedCollectionEntries",
62905
+ 1,
62906
+ "produced collection entry"
61982
62907
  );
61983
- if (result.kind === "return") {
61984
- consumeBudget(
61985
- ctx,
61986
- "producedCollectionEntries",
61987
- 1,
61988
- "produced collection entry"
61989
- );
61990
- out.push(result.value);
61991
- }
62908
+ out.push(value);
61992
62909
  return 0 /* Continue */;
61993
62910
  });
61994
62911
  return out;
@@ -63324,7 +64241,7 @@ function encodeRequiredConstructorArgument(args) {
63324
64241
  return adoptTracked(trackedId, "root");
63325
64242
  }
63326
64243
  if (args.typeInfo.type !== 6 /* List */ && args.typeInfo.type !== 5 /* Dictionary */) {
63327
- return args.value;
64244
+ return cloneConstructorLiteralValue(args.value);
63328
64245
  }
63329
64246
  if (args.value === null || args.value === void 0) return null;
63330
64247
  if (trackedId !== null) return adoptTracked(trackedId, "root");
@@ -63368,7 +64285,7 @@ function encodeRequiredConstructorArgument(args) {
63368
64285
  }
63369
64286
  return nested;
63370
64287
  }
63371
- return registerRow(value).id;
64288
+ return registerRow(cloneConstructorLiteralValue(value)).id;
63372
64289
  };
63373
64290
  if (collectionType.type === 6 /* List */) {
63374
64291
  if (!Array.isArray(args.value)) {
@@ -63962,10 +64879,10 @@ function cloneConstructorArgumentGraph(args) {
63962
64879
  return cloneRow(args.source, args.member, args.genericEnv);
63963
64880
  }
63964
64881
  function cloneConstructorLiteralValue(value) {
64882
+ if (typeof value !== "object" || value === null) return value;
63965
64883
  if (Array.isArray(value)) {
63966
64884
  return value.map((entry) => cloneConstructorLiteralValue(entry));
63967
64885
  }
63968
- if (typeof value !== "object" || value === null) return value;
63969
64886
  return Object.fromEntries(
63970
64887
  Object.entries(value).map(([key, entry]) => [
63971
64888
  key,
@@ -64541,17 +65458,6 @@ function iterateCollection(c, ctx, callback) {
64541
65458
  return callback(entry, key, valueId);
64542
65459
  });
64543
65460
  }
64544
- function pushParams(parent, parameters, positional, isList) {
64545
- const child = createChildScope(parent);
64546
- if (parameters.length === 1) {
64547
- child.setLocal(parameters[0].id, positional[1]);
64548
- } else if (parameters.length === 2) {
64549
- const first = isList ? Number(positional[0]) : String(positional[0]);
64550
- child.setLocal(parameters[0].id, first);
64551
- child.setLocal(parameters[1].id, positional[1]);
64552
- }
64553
- return child;
64554
- }
64555
65461
  var DELEGATE_LEXICAL_THIS, DELEGATE_LEXICAL_ROOT, NonCatchableNSGetterRuntimeError, NativeFunctionDelegateUnavailableError, CorruptNeoScriptIRError, NeoScriptResourceLimitError, NeoScriptWallClockTimeoutError, DEFAULT_NEO_SCRIPT_EXECUTION_BUDGET_LIMITS, liveListIndexesByProject, evaluatorOwnershipCachesByBase, MAX_CONSTRUCTION_DEPTH, MAX_LOOP_ITERATIONS, resolutionCacheByMembers, NO_SCHEMA_REVISION, LazyValueOverlay, READONLY_FOREACH_BINDING_ERROR, READONLY_CATCH_BINDING_ERROR;
64556
65462
  var init_evaluateNSGetter = __esm({
64557
65463
  "../src/view-models/neoscript-evaluator/evaluateNSGetter.ts"() {
@@ -64565,6 +65471,7 @@ var init_evaluateNSGetter = __esm({
64565
65471
  init_neoscript();
64566
65472
  init_NSGetterRuntimeError();
64567
65473
  init_value_row_owner_members();
65474
+ init_src();
64568
65475
  init_decimal();
64569
65476
  init_members();
64570
65477
  init_core();
@@ -66503,6 +67410,7 @@ var init_project_document_read = __esm({
66503
67410
  });
66504
67411
 
66505
67412
  // src/workspace.ts
67413
+ import { createHash as createHash2 } from "node:crypto";
66506
67414
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "node:fs";
66507
67415
  import { dirname, join as join2, resolve } from "node:path";
66508
67416
  function recordStateKey(recordKind, recordId) {
@@ -66735,9 +67643,12 @@ function matchUnityScalarField(content, field) {
66735
67643
  function readWorkspaceState(root, options = {}) {
66736
67644
  const statePath = join2(root, NEO_STATE_DIR, NEO_STATE_FILE);
66737
67645
  if (!existsSync2(statePath)) {
67646
+ options.onSourceRead?.(null);
66738
67647
  return { records: {} };
66739
67648
  }
66740
- const parsed = JSON.parse(readFileSync2(statePath, "utf8"));
67649
+ const source = readFileSync2(statePath, "utf8");
67650
+ options.onSourceRead?.(source);
67651
+ const parsed = JSON.parse(source);
66741
67652
  if (typeof parsed !== "object" || parsed === null) {
66742
67653
  throw new Error(`"${statePath}" must contain a JSON object.`);
66743
67654
  }
@@ -66780,10 +67691,20 @@ function loadWorkspace(startDir, options = {}) {
66780
67691
  `No "${NEO_CONFIG_FILE}" found in "${startDir}" or any parent directory. Run "neo init" first.`
66781
67692
  );
66782
67693
  }
67694
+ let stateSourceSha256;
67695
+ const state = readWorkspaceState(root, {
67696
+ discardLegacyFormat2State: options.discardLegacyFormat2State,
67697
+ ...options.fingerprintStateSource === true ? {
67698
+ onSourceRead: (source) => {
67699
+ stateSourceSha256 = createHash2("sha256").update(source ?? "<missing>").digest("hex");
67700
+ }
67701
+ } : {}
67702
+ });
66783
67703
  return {
66784
67704
  root,
66785
67705
  config: readWorkspaceConfig(root),
66786
- state: readWorkspaceState(root, options)
67706
+ state,
67707
+ ...stateSourceSha256 === void 0 ? {} : { stateSourceSha256 }
66787
67708
  };
66788
67709
  }
66789
67710
  var NEO_CONFIG_FILE, NEO_STATE_DIR, NEO_STATE_FILE, CURRENT_FORMAT_VERSION;
@@ -72142,7 +73063,7 @@ var init_animation_clips = __esm({
72142
73063
 
72143
73064
  // src/project-source/materialized-construction-cache.ts
72144
73065
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
72145
- import { createHash as createHash2 } from "node:crypto";
73066
+ import { createHash as createHash3 } from "node:crypto";
72146
73067
  import { dirname as dirname2, join as join3 } from "node:path";
72147
73068
  function createMaterializedConstructionExpressionsV1(args) {
72148
73069
  const warm = args.useBuildCaches ? readMaterializedConstructionBuildCacheV1(args.root, args.state) : null;
@@ -72234,7 +73155,7 @@ function writeMaterializedConstructionBuildCacheV1(root, state, expressions) {
72234
73155
  renameSync(temporary, file);
72235
73156
  }
72236
73157
  function materializedConstructionStateFingerprint(state) {
72237
- const hash = createHash2("sha256");
73158
+ const hash = createHash3("sha256");
72238
73159
  hash.update(String(MATERIALIZED_CONSTRUCTION_BUILD_CACHE_REVISION));
72239
73160
  for (const [key, record3] of Object.entries(state.records).sort(
72240
73161
  ([left], [right]) => left.localeCompare(right)
@@ -73060,10 +73981,10 @@ var init_neo_script_recompile_scope = __esm({
73060
73981
  });
73061
73982
 
73062
73983
  // ../src/database/project-content-hash.ts
73063
- import { createHash as createHash3 } from "node:crypto";
73984
+ import { createHash as createHash4 } from "node:crypto";
73064
73985
  function hashCanonicalJson(value) {
73065
73986
  const canonicalJson = canonicalJsonStringify(value);
73066
- return createHash3("sha256").update(canonicalJson).digest("hex");
73987
+ return createHash4("sha256").update(canonicalJson).digest("hex");
73067
73988
  }
73068
73989
  var init_project_content_hash = __esm({
73069
73990
  "../src/database/project-content-hash.ts"() {
@@ -73130,7 +74051,7 @@ var init_project_fingerprint = __esm({
73130
74051
 
73131
74052
  // src/project-source/neoscript-build-cache.ts
73132
74053
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
73133
- import { createHash as createHash4 } from "node:crypto";
74054
+ import { createHash as createHash5 } from "node:crypto";
73134
74055
  import { dirname as dirname3, join as join4 } from "node:path";
73135
74056
  function loadOrBuildNeoScriptProjectV1(root, document) {
73136
74057
  const fingerprint = neoScriptProjectFingerprint(document);
@@ -73157,7 +74078,7 @@ function loadOrBuildNeoScriptProjectV1(root, document) {
73157
74078
  return project;
73158
74079
  }
73159
74080
  function neoScriptProjectFingerprint(document) {
73160
- return createHash4("sha256").update(
74081
+ return createHash5("sha256").update(
73161
74082
  canonicalJsonStringify(neoScriptCompilationProjectContract(document))
73162
74083
  ).digest("hex");
73163
74084
  }
@@ -92280,7 +93201,7 @@ var init_project_documents = __esm({
92280
93201
 
92281
93202
  // src/project-source/project-document-cache.ts
92282
93203
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
92283
- import { createHash as createHash5 } from "node:crypto";
93204
+ import { createHash as createHash6 } from "node:crypto";
92284
93205
  import { dirname as dirname4, join as join5 } from "node:path";
92285
93206
  function readProjectSourceAnalysisBuildCacheV4(root, sources) {
92286
93207
  try {
@@ -92404,7 +93325,7 @@ function writeProjectSourceDocumentBuildCacheV1(root, sources, documents) {
92404
93325
  renameSync3(temporary, file);
92405
93326
  }
92406
93327
  function projectSourceFileFingerprint(source) {
92407
- return createHash5("sha256").update(String(PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION)).update("\0").update(String(NEOSCRIPT_COMPILER_REVISION)).update("\0").update(source.kind).update("\0").update(source.text).digest("hex");
93328
+ return createHash6("sha256").update(String(PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION)).update("\0").update(String(NEOSCRIPT_COMPILER_REVISION)).update("\0").update(source.kind).update("\0").update(source.text).digest("hex");
92408
93329
  }
92409
93330
  function isCachedProjectSourceDocument(value, source) {
92410
93331
  if (value === null || typeof value !== "object") return false;
@@ -92412,7 +93333,7 @@ function isCachedProjectSourceDocument(value, source) {
92412
93333
  return document.kind === source.kind && document.sourceText === source.text && Array.isArray(document.declarations) && Array.isArray(document.diagnostics);
92413
93334
  }
92414
93335
  function projectSourceFingerprint(sources) {
92415
- const hash = createHash5("sha256");
93336
+ const hash = createHash6("sha256");
92416
93337
  hash.update(String(PROJECT_SOURCE_BUILD_CACHE_REVISION));
92417
93338
  hash.update("\0");
92418
93339
  hash.update(String(NEOSCRIPT_COMPILER_REVISION));
@@ -92440,7 +93361,7 @@ var init_project_document_cache = __esm({
92440
93361
  });
92441
93362
 
92442
93363
  // src/project-source/project-files.ts
92443
- import { createHash as createHash6 } from "node:crypto";
93364
+ import { createHash as createHash7 } from "node:crypto";
92444
93365
  import {
92445
93366
  existsSync as existsSync3,
92446
93367
  mkdirSync as mkdirSync6,
@@ -92687,7 +93608,7 @@ function discoverProjectBinariesV4(root, explicit, ignoredPaths = []) {
92687
93608
  }).sort((left, right) => compareCodePoints(left.path, right.path));
92688
93609
  }
92689
93610
  function sha256Bytes(bytes) {
92690
- return createHash6("sha256").update(bytes).digest("hex");
93611
+ return createHash7("sha256").update(bytes).digest("hex");
92691
93612
  }
92692
93613
  function sha256File(path) {
92693
93614
  return sha256Bytes(readFileSync6(path));
@@ -98140,7 +99061,7 @@ var init_script = __esm({
98140
99061
  });
98141
99062
 
98142
99063
  // ../src/database/project-source-identity.ts
98143
- import { createHash as createHash7 } from "node:crypto";
99064
+ import { createHash as createHash8 } from "node:crypto";
98144
99065
  function hashProjectSourceFiles(inputFiles) {
98145
99066
  const files = normalizeSourceFiles(inputFiles);
98146
99067
  const bytes = Buffer.from(JSON.stringify({ version: 1, files }), "utf8");
@@ -98149,7 +99070,7 @@ function hashProjectSourceFiles(inputFiles) {
98149
99070
  `Project source identity is ${bytes.byteLength} bytes; the limit is ${MAX_SOURCE_BYTES} bytes.`
98150
99071
  );
98151
99072
  }
98152
- return createHash7("sha256").update(bytes).digest("hex");
99073
+ return createHash8("sha256").update(bytes).digest("hex");
98153
99074
  }
98154
99075
  function normalizeSourceFiles(inputFiles) {
98155
99076
  if (inputFiles.length > MAX_SOURCE_FILES) {
@@ -98241,14 +99162,14 @@ var init_project_source_identity = __esm({
98241
99162
 
98242
99163
  // src/push-hook.ts
98243
99164
  import { spawn } from "node:child_process";
98244
- import { createHash as createHash8 } from "node:crypto";
99165
+ import { createHash as createHash9 } from "node:crypto";
98245
99166
  import { existsSync as existsSync10, readFileSync as readFileSync13, readdirSync as readdirSync4 } from "node:fs";
98246
99167
  import { join as join13, relative as relative3, sep as sep3 } from "node:path";
98247
- function fingerprintPushInputs(workspace) {
99168
+ function fingerprintPushInputs(workspace, options = {}) {
98248
99169
  const paths = /* @__PURE__ */ new Set([
98249
99170
  join13(workspace.root, "neo.json"),
98250
99171
  ...listProjectSourceFilesV4(workspace.root),
98251
- ...listProjectTestFilesV1(workspace.root),
99172
+ ...options.includeTests === false ? [] : listProjectTestFilesV1(workspace.root),
98252
99173
  ...workspace.config.unityConfigPath === void 0 ? [] : [join13(workspace.root, workspace.config.unityConfigPath)]
98253
99174
  ]);
98254
99175
  const visitManaged = (directory) => {
@@ -98262,7 +99183,7 @@ function fingerprintPushInputs(workspace) {
98262
99183
  };
98263
99184
  visitManaged(join13(workspace.root, "Files", "Images"));
98264
99185
  visitManaged(join13(workspace.root, "Files", "AudioClips"));
98265
- const hash = createHash8("sha256");
99186
+ const hash = createHash9("sha256");
98266
99187
  for (const path of [...paths].sort()) {
98267
99188
  const name = relative3(workspace.root, path).split(sep3).join("/");
98268
99189
  hash.update(name);
@@ -98919,7 +99840,7 @@ __export(push_exports, {
98919
99840
  runPush: () => runPush,
98920
99841
  stripServerDerivedNeoScript: () => stripServerDerivedNeoScript
98921
99842
  });
98922
- import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
99843
+ import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
98923
99844
  import {
98924
99845
  mkdirSync as mkdirSync10,
98925
99846
  writeFileSync as writeFileSync10,
@@ -99702,7 +100623,7 @@ async function runPush(workspace, options, preparationOverride) {
99702
100623
  version: 1,
99703
100624
  projectFingerprint: `sha256:${preparedLocal.source.sourceHash}`,
99704
100625
  inputFingerprint: preparedInputFingerprint,
99705
- documentSha256: createHash9("sha256").update(documentJson).digest("hex"),
100626
+ documentSha256: createHash10("sha256").update(documentJson).digest("hex"),
99706
100627
  document: candidate.document
99707
100628
  })}
99708
100629
  `,
@@ -101711,7 +102632,7 @@ var init_registry2 = __esm({
101711
102632
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
101712
102633
  formatVersion: 3,
101713
102634
  contractVersion: "3.9",
101714
- cliVersion: "0.26.1",
102635
+ cliVersion: "0.26.3",
101715
102636
  projectFileUploadBatchSize: 32,
101716
102637
  documentRecords: {
101717
102638
  member: {
@@ -103004,7 +103925,7 @@ __export(test_exports, {
103004
103925
  maintainNeoTestBuildCache: () => maintainNeoTestBuildCache,
103005
103926
  runTest: () => runTest
103006
103927
  });
103007
- import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
103928
+ import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
103008
103929
  import {
103009
103930
  existsSync as existsSync12,
103010
103931
  mkdirSync as mkdirSync11,
@@ -103025,6 +103946,7 @@ import {
103025
103946
  resolve as resolve3,
103026
103947
  sep as sep5
103027
103948
  } from "node:path";
103949
+ import { isDeepStrictEqual } from "node:util";
103028
103950
  function isRecord10(value) {
103029
103951
  return typeof value === "object" && value !== null && !Array.isArray(value);
103030
103952
  }
@@ -103108,9 +104030,46 @@ function stableTestValue(value) {
103108
104030
  function display(value) {
103109
104031
  return stableTestValue(value);
103110
104032
  }
103111
- function assertMatcher(condition, expectationValue, message, expected, received = expectationValue.actual) {
103112
- if (condition === expectationValue.negated)
103113
- throw new NeoTestAssertionError(message, expected, received);
104033
+ function matcherFailureMessage(kind, expectationValue, expected, received, details) {
104034
+ const actual = expectationValue.actual;
104035
+ const negation = expectationValue.negated ? "not " : "";
104036
+ switch (kind) {
104037
+ case "toBe":
104038
+ return `Expected ${display(actual)} ${negation}to be ${display(expected)}.`;
104039
+ case "toEqual":
104040
+ return `Expected ${display(actual)} ${negation}to equal ${display(expected)}.`;
104041
+ case "toBeNull":
104042
+ return `Expected ${display(actual)} ${negation}to be null.`;
104043
+ case "boolean":
104044
+ return `Expected ${display(actual)} ${negation}to be ${String(expected)}.`;
104045
+ case "numeric":
104046
+ return `Expected ${display(actual)} ${negation}to satisfy numeric comparison with ${display(expected)}.`;
104047
+ case "toContain":
104048
+ return `Expected ${display(actual)} ${negation}to contain ${display(expected)}.`;
104049
+ case "toHaveLength":
104050
+ return `Expected length ${String(expected)}, received ${String(received)}.`;
104051
+ case "toMatchObject":
104052
+ return `Expected ${display(actual)} ${negation}to match ${display(expected)}.`;
104053
+ case "toThrow":
104054
+ return `Expected callback ${negation}to throw${details === void 0 ? "" : ` a message containing ${display(details)}`}.`;
104055
+ case "mock":
104056
+ return `Expected mock ${negation}to match recorded calls ${display(details)}.`;
104057
+ }
104058
+ }
104059
+ function assertMatcher(condition, expectationValue, messageKind, expected, received = expectationValue.actual, details) {
104060
+ if (condition === expectationValue.negated) {
104061
+ throw new NeoTestAssertionError(
104062
+ matcherFailureMessage(
104063
+ messageKind,
104064
+ expectationValue,
104065
+ expected,
104066
+ received,
104067
+ details
104068
+ ),
104069
+ expected,
104070
+ received
104071
+ );
104072
+ }
103114
104073
  }
103115
104074
  function errorMessage2(error) {
103116
104075
  return error instanceof Error ? error.message : String(error);
@@ -103118,10 +104077,11 @@ function errorMessage2(error) {
103118
104077
  function findDelegateTargetMemberId(value) {
103119
104078
  return isRecord10(value) && typeof value.memberId === "string" ? value.memberId : null;
103120
104079
  }
103121
- function makeEvaluatorContext(rawDocument, interceptor, documentAlreadyIsolated = false) {
103122
- const isolatedDocument = documentAlreadyIsolated ? rawDocument : structuredClone(rawDocument);
103123
- const document = readDocumentArrays(isolatedDocument);
103124
- const constructors = Array.isArray(isolatedDocument.constructors) ? isolatedDocument.constructors.filter(isRecord10) : [];
104080
+ function sharedEvaluatorBase(rawDocument) {
104081
+ const cached = SHARED_EVALUATOR_BASES.get(rawDocument);
104082
+ if (cached !== void 0) return cached;
104083
+ const document = readDocumentArrays(rawDocument);
104084
+ const constructors = Array.isArray(rawDocument.constructors) ? rawDocument.constructors.filter(isRecord10) : [];
103125
104085
  const vm = {
103126
104086
  project: document.project,
103127
104087
  members: document.members,
@@ -103146,10 +104106,19 @@ function makeEvaluatorContext(rawDocument, interceptor, documentAlreadyIsolated
103146
104106
  }
103147
104107
  )
103148
104108
  };
103149
- return createNeoScriptEvaluationRuntime({
104109
+ const created = {
103150
104110
  vm,
104111
+ rootValue: buildRootValue(document)
104112
+ };
104113
+ SHARED_EVALUATOR_BASES.set(rawDocument, created);
104114
+ return created;
104115
+ }
104116
+ function makeEvaluatorContext(rawDocument, interceptor) {
104117
+ const base = sharedEvaluatorBase(rawDocument);
104118
+ return createNeoScriptEvaluationRuntime({
104119
+ vm: base.vm,
103151
104120
  thisValue: null,
103152
- rootValue: buildRootValue(document),
104121
+ rootValue: base.rootValue,
103153
104122
  dialogueContext: null,
103154
104123
  callInterceptor: interceptor,
103155
104124
  nativeFunctionErrorCheckBehavior: "strict"
@@ -103216,14 +104185,15 @@ function preparedHookCandidate(workspace) {
103216
104185
  );
103217
104186
  }
103218
104187
  const documentJson = JSON.stringify(parsed.document);
103219
- if (createHash10("sha256").update(documentJson).digest("hex") !== parsed.documentSha256) {
104188
+ if (createHash11("sha256").update(documentJson).digest("hex") !== parsed.documentSha256) {
103220
104189
  throw new NeoTestPreparedCandidateError(
103221
104190
  "Configured push hook candidate project document failed checksum verification."
103222
104191
  );
103223
104192
  }
103224
104193
  return {
103225
104194
  document: parsed.document,
103226
- sourceHash: configuredFingerprint.replace(/^sha256:/u, "")
104195
+ sourceHash: configuredFingerprint.replace(/^sha256:/u, ""),
104196
+ documentSha256: parsed.documentSha256
103227
104197
  };
103228
104198
  } catch (error) {
103229
104199
  if (error instanceof NeoTestPreparedCandidateError) throw error;
@@ -103232,11 +104202,79 @@ function preparedHookCandidate(workspace) {
103232
104202
  );
103233
104203
  }
103234
104204
  }
104205
+ function fingerprintTestCandidateInputs(workspace) {
104206
+ return createHash11("sha256").update("neo-test-candidate\0").update(String(TEST_CANDIDATE_CACHE_REVISION)).update("\0").update(PROJECT_SCHEMA_CONTRACT.cliVersion).update("\0").update(String(NEOSCRIPT_COMPILER_REVISION)).update("\0").update(fingerprintPushInputs(workspace, { includeTests: false })).update("\0").update(
104207
+ workspace.stateSourceSha256 === void 0 ? `object\0${JSON.stringify(workspace.state)}` : `source\0${workspace.stateSourceSha256}`
104208
+ ).digest("hex");
104209
+ }
104210
+ function testCandidateCachePath(workspace, inputFingerprint) {
104211
+ const cacheKey = createHash11("sha256").update(inputFingerprint).digest("hex");
104212
+ return join16(
104213
+ workspace.root,
104214
+ ".neo",
104215
+ "test-build",
104216
+ "v1",
104217
+ "project",
104218
+ cacheKey,
104219
+ "candidate.json"
104220
+ );
104221
+ }
104222
+ function cachedTestCandidate(workspace, inputFingerprint) {
104223
+ try {
104224
+ const candidatePath = testCandidateCachePath(workspace, inputFingerprint);
104225
+ const parsed = JSON.parse(readFileSync16(candidatePath, "utf8"));
104226
+ if (!isRecord10(parsed)) return null;
104227
+ if (parsed.version !== 1) return null;
104228
+ if (parsed.candidateRevision !== TEST_CANDIDATE_CACHE_REVISION) return null;
104229
+ if (parsed.cliVersion !== PROJECT_SCHEMA_CONTRACT.cliVersion) return null;
104230
+ if (parsed.compilerRevision !== NEOSCRIPT_COMPILER_REVISION) return null;
104231
+ if (parsed.inputFingerprint !== inputFingerprint) return null;
104232
+ if (typeof parsed.sourceHash !== "string" || parsed.sourceHash.length === 0) {
104233
+ return null;
104234
+ }
104235
+ if (typeof parsed.documentSha256 !== "string") return null;
104236
+ const documentJson = readFileSync16(
104237
+ join16(dirname9(candidatePath), "document.json"),
104238
+ "utf8"
104239
+ );
104240
+ if (createHash11("sha256").update(documentJson).digest("hex") !== parsed.documentSha256) {
104241
+ return null;
104242
+ }
104243
+ const document = JSON.parse(documentJson);
104244
+ if (!isRecord10(document)) return null;
104245
+ return {
104246
+ document,
104247
+ sourceHash: parsed.sourceHash,
104248
+ documentSha256: parsed.documentSha256
104249
+ };
104250
+ } catch {
104251
+ return null;
104252
+ }
104253
+ }
104254
+ function cacheTestCandidate(workspace, inputFingerprint, candidate) {
104255
+ const documentJson = JSON.stringify(candidate.document);
104256
+ const documentSha256 = createHash11("sha256").update(documentJson).digest("hex");
104257
+ const candidatePath = testCandidateCachePath(workspace, inputFingerprint);
104258
+ atomicWrite(join16(dirname9(candidatePath), "document.json"), documentJson);
104259
+ atomicWrite(
104260
+ candidatePath,
104261
+ JSON.stringify({
104262
+ version: 1,
104263
+ candidateRevision: TEST_CANDIDATE_CACHE_REVISION,
104264
+ cliVersion: PROJECT_SCHEMA_CONTRACT.cliVersion,
104265
+ compilerRevision: NEOSCRIPT_COMPILER_REVISION,
104266
+ inputFingerprint,
104267
+ sourceHash: candidate.sourceHash,
104268
+ documentSha256
104269
+ }) + "\n"
104270
+ );
104271
+ return { ...candidate, documentSha256 };
104272
+ }
103235
104273
  function compileSpec(workspace, document, absolutePath, projectCompilationHash2) {
103236
104274
  const scriptDocument = readDocumentArrays(document);
103237
104275
  const path = relative5(workspace.root, absolutePath).split(sep5).join("/");
103238
104276
  const source = readFileSync16(absolutePath, "utf8");
103239
- const sourceHash = createHash10("sha256").update(source).digest("hex");
104277
+ const sourceHash = createHash11("sha256").update(source).digest("hex");
103240
104278
  const artifactPath = join16(
103241
104279
  workspace.root,
103242
104280
  ".neo",
@@ -103247,7 +104285,7 @@ function compileSpec(workspace, document, absolutePath, projectCompilationHash2)
103247
104285
  );
103248
104286
  try {
103249
104287
  const cached = JSON.parse(readFileSync16(artifactPath, "utf8"));
103250
- if (isRecord10(cached) && cached.version === 1 && cached.compilerRevision === NEOSCRIPT_COMPILER_REVISION && cached.projectCompilationHash === projectCompilationHash2 && cached.sourceHash === sourceHash && isRecord10(cached.action) && typeof cached.artifactSha256 === "string" && createHash10("sha256").update(JSON.stringify(cached.action)).digest("hex") === cached.artifactSha256 && cached.action.compilerRevision === NEOSCRIPT_COMPILER_REVISION && Array.isArray(cached.action.parameters) && Array.isArray(cached.action.instructions) && isRecord10(cached.action.typeInfo)) {
104288
+ if (isRecord10(cached) && cached.version === 1 && cached.compilerRevision === NEOSCRIPT_COMPILER_REVISION && cached.projectCompilationHash === projectCompilationHash2 && cached.sourceHash === sourceHash && isRecord10(cached.action) && typeof cached.artifactSha256 === "string" && createHash11("sha256").update(JSON.stringify(cached.action)).digest("hex") === cached.artifactSha256 && cached.action.compilerRevision === NEOSCRIPT_COMPILER_REVISION && Array.isArray(cached.action.parameters) && Array.isArray(cached.action.instructions) && isRecord10(cached.action.typeInfo)) {
103251
104289
  return {
103252
104290
  path,
103253
104291
  source,
@@ -103279,15 +104317,16 @@ function compileSpec(workspace, document, absolutePath, projectCompilationHash2)
103279
104317
  compilerRevision: NEOSCRIPT_COMPILER_REVISION,
103280
104318
  projectCompilationHash: projectCompilationHash2,
103281
104319
  sourceHash,
103282
- artifactSha256: createHash10("sha256").update(JSON.stringify(compiled.action)).digest("hex"),
104320
+ artifactSha256: createHash11("sha256").update(JSON.stringify(compiled.action)).digest("hex"),
103283
104321
  action: compiled.action
103284
104322
  })}
103285
104323
  `
103286
104324
  );
103287
104325
  return compiled;
103288
104326
  }
103289
- function projectCompilationHash(projectSourceHash, document) {
103290
- return createHash10("sha256").update(projectSourceHash).update("\0").update(JSON.stringify(document)).digest("hex");
104327
+ function projectCompilationHash(projectSourceHash, document, knownDocumentSha256) {
104328
+ const documentSha256 = knownDocumentSha256 ?? createHash11("sha256").update(JSON.stringify(document)).digest("hex");
104329
+ return createHash11("sha256").update(projectSourceHash).update("\0").update(documentSha256).digest("hex");
103291
104330
  }
103292
104331
  function selectedSpecPaths(workspace, selectors) {
103293
104332
  const all = listProjectTestFilesV1(workspace.root);
@@ -103520,7 +104559,7 @@ function registerSpec(spec, document) {
103520
104559
  }
103521
104560
  names.add(fullName);
103522
104561
  const test = {
103523
- id: `sha256:${createHash10("sha256").update(`${spec.path}\0${namePath}`).digest("hex")}`,
104562
+ id: `sha256:${createHash11("sha256").update(`${spec.path}\0${namePath}`).digest("hex")}`,
103524
104563
  name: namePath,
103525
104564
  fullName,
103526
104565
  body: portableDelegate(call.args[1]),
@@ -103579,8 +104618,7 @@ function mockResponse(environment, mock, selected2, call) {
103579
104618
  };
103580
104619
  }
103581
104620
  function partialObjectMatch(actual, expected) {
103582
- if (!isRecord10(expected))
103583
- return stableTestValue(actual) === stableTestValue(expected);
104621
+ if (!isRecord10(expected)) return isDeepStrictEqual(actual, expected);
103584
104622
  if (!isRecord10(actual)) return false;
103585
104623
  return Object.entries(expected).every(
103586
104624
  ([key, value]) => partialObjectMatch(actual[key], value)
@@ -103737,56 +104775,28 @@ function interceptTestCallCore(environment, call) {
103737
104775
  }
103738
104776
  return { handled: true, value: null };
103739
104777
  }
103740
- const matcherIds = /* @__PURE__ */ new Set([
103741
- NEO_TEST_IDS.toBe,
103742
- NEO_TEST_IDS.toEqual,
103743
- NEO_TEST_IDS.toBeNull,
103744
- NEO_TEST_IDS.toBeTrue,
103745
- NEO_TEST_IDS.toBeFalse,
103746
- NEO_TEST_IDS.toBeGreaterThan,
103747
- NEO_TEST_IDS.toBeGreaterThanOrEqual,
103748
- NEO_TEST_IDS.toBeLessThan,
103749
- NEO_TEST_IDS.toBeLessThanOrEqual,
103750
- NEO_TEST_IDS.toContain,
103751
- NEO_TEST_IDS.toHaveLength,
103752
- NEO_TEST_IDS.toMatchObject,
103753
- NEO_TEST_IDS.toThrow,
103754
- NEO_TEST_IDS.toHaveBeenCalled,
103755
- NEO_TEST_IDS.toHaveBeenCalledTimes,
103756
- NEO_TEST_IDS.toHaveBeenCalledWith
103757
- ]);
103758
- if (!matcherIds.has(call.memberId)) return { handled: false };
104778
+ if (!MATCHER_IDS.has(call.memberId)) return { handled: false };
103759
104779
  const expectationValue = readExpectation(call.receiver);
103760
104780
  const actual = expectationValue.actual;
103761
104781
  if (call.memberId === NEO_TEST_IDS.toBe) {
103762
104782
  assertMatcher(
103763
104783
  Object.is(actual, call.args[0]),
103764
104784
  expectationValue,
103765
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to be ${display(call.args[0])}.`,
104785
+ "toBe",
103766
104786
  call.args[0]
103767
104787
  );
103768
104788
  } else if (call.memberId === NEO_TEST_IDS.toEqual) {
103769
104789
  assertMatcher(
103770
- stableTestValue(actual) === stableTestValue(call.args[0]),
104790
+ isDeepStrictEqual(actual, call.args[0]),
103771
104791
  expectationValue,
103772
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to equal ${display(call.args[0])}.`,
104792
+ "toEqual",
103773
104793
  call.args[0]
103774
104794
  );
103775
104795
  } else if (call.memberId === NEO_TEST_IDS.toBeNull) {
103776
- assertMatcher(
103777
- actual === null,
103778
- expectationValue,
103779
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to be null.`,
103780
- null
103781
- );
104796
+ assertMatcher(actual === null, expectationValue, "toBeNull", null);
103782
104797
  } else if (call.memberId === NEO_TEST_IDS.toBeTrue || call.memberId === NEO_TEST_IDS.toBeFalse) {
103783
104798
  const expected = call.memberId === NEO_TEST_IDS.toBeTrue;
103784
- assertMatcher(
103785
- actual === expected,
103786
- expectationValue,
103787
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to be ${String(expected)}.`,
103788
- expected
103789
- );
104799
+ assertMatcher(actual === expected, expectationValue, "boolean", expected);
103790
104800
  } else if (call.memberId === NEO_TEST_IDS.toBeGreaterThan || call.memberId === NEO_TEST_IDS.toBeGreaterThanOrEqual || call.memberId === NEO_TEST_IDS.toBeLessThan || call.memberId === NEO_TEST_IDS.toBeLessThanOrEqual) {
103791
104801
  const left = Number(actual);
103792
104802
  const right = Number(call.args[0]);
@@ -103794,25 +104804,18 @@ function interceptTestCallCore(environment, call) {
103794
104804
  assertMatcher(
103795
104805
  Number.isFinite(left) && Number.isFinite(right) && matches,
103796
104806
  expectationValue,
103797
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to satisfy numeric comparison with ${display(call.args[0])}.`,
104807
+ "numeric",
103798
104808
  call.args[0]
103799
104809
  );
103800
104810
  } else if (call.memberId === NEO_TEST_IDS.toContain) {
103801
- const contains2 = typeof actual === "string" ? actual.includes(String(call.args[0])) : Array.isArray(actual) && actual.some(
103802
- (entry) => stableTestValue(entry) === stableTestValue(call.args[0])
103803
- );
103804
- assertMatcher(
103805
- contains2,
103806
- expectationValue,
103807
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to contain ${display(call.args[0])}.`,
103808
- call.args[0]
103809
- );
104811
+ const contains2 = typeof actual === "string" ? actual.includes(String(call.args[0])) : Array.isArray(actual) && actual.some((entry) => isDeepStrictEqual(entry, call.args[0]));
104812
+ assertMatcher(contains2, expectationValue, "toContain", call.args[0]);
103810
104813
  } else if (call.memberId === NEO_TEST_IDS.toHaveLength) {
103811
104814
  const length = typeof actual === "string" || Array.isArray(actual) ? actual.length : isRecord10(actual) ? Object.keys(actual).length : -1;
103812
104815
  assertMatcher(
103813
104816
  length === call.args[0],
103814
104817
  expectationValue,
103815
- `Expected length ${String(call.args[0])}, received ${String(length)}.`,
104818
+ "toHaveLength",
103816
104819
  call.args[0],
103817
104820
  length
103818
104821
  );
@@ -103820,7 +104823,7 @@ function interceptTestCallCore(environment, call) {
103820
104823
  assertMatcher(
103821
104824
  partialObjectMatch(actual, call.args[0]),
103822
104825
  expectationValue,
103823
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to match ${display(call.args[0])}.`,
104826
+ "toMatchObject",
103824
104827
  call.args[0]
103825
104828
  );
103826
104829
  } else if (call.memberId === NEO_TEST_IDS.toThrow) {
@@ -103836,9 +104839,10 @@ function interceptTestCallCore(environment, call) {
103836
104839
  assertMatcher(
103837
104840
  matches,
103838
104841
  expectationValue,
103839
- `Expected callback ${expectationValue.negated ? "not " : ""}to throw${pattern === void 0 ? "" : ` a message containing ${display(pattern)}`}.`,
104842
+ "toThrow",
103840
104843
  pattern ?? "a catchable NeoScript error",
103841
- thrown === void 0 ? "no throw" : errorMessage2(thrown)
104844
+ thrown === void 0 ? "no throw" : errorMessage2(thrown),
104845
+ pattern
103842
104846
  );
103843
104847
  } else {
103844
104848
  const handle = readMockHandle(actual);
@@ -103848,15 +104852,14 @@ function interceptTestCallCore(environment, call) {
103848
104852
  "Expected value is not an active mock handle."
103849
104853
  );
103850
104854
  }
103851
- const matches = call.memberId === NEO_TEST_IDS.toHaveBeenCalled ? mock.calls.length > 0 : call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length === call.args[0] : mock.calls.some(
103852
- (args) => stableTestValue(args) === stableTestValue(call.args)
103853
- );
104855
+ const matches = call.memberId === NEO_TEST_IDS.toHaveBeenCalled ? mock.calls.length > 0 : call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length === call.args[0] : mock.calls.some((args) => isDeepStrictEqual(args, call.args));
103854
104856
  assertMatcher(
103855
104857
  matches,
103856
104858
  expectationValue,
103857
- `Expected mock ${expectationValue.negated ? "not " : ""}to match recorded calls ${display(mock.calls)}.`,
104859
+ "mock",
103858
104860
  call.memberId === NEO_TEST_IDS.toHaveBeenCalled ? "at least one call" : call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? call.args[0] : call.args,
103859
- call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length : mock.calls
104861
+ call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length : mock.calls,
104862
+ mock.calls
103860
104863
  );
103861
104864
  }
103862
104865
  return { handled: true, value: null };
@@ -103871,24 +104874,32 @@ function interceptTestCall(environment, call) {
103871
104874
  throw error;
103872
104875
  }
103873
104876
  }
103874
- function createTestEnvironment(document, documentAlreadyIsolated = false) {
104877
+ function createTestEnvironment(document) {
103875
104878
  const environment = {
103876
104879
  baseDocument: document,
103877
104880
  context: void 0,
103878
104881
  mocks: /* @__PURE__ */ new Map(),
103879
104882
  mocksByMember: /* @__PURE__ */ new Map(),
103880
- nextMockId: 1
104883
+ nextMockId: 1,
104884
+ hasStateChanges: false
103881
104885
  };
103882
104886
  environment.context = makeEvaluatorContext(
103883
104887
  document,
103884
- (call) => interceptTestCall(environment, call),
103885
- documentAlreadyIsolated
104888
+ (call) => interceptTestCall(environment, call)
103886
104889
  );
103887
104890
  return environment;
103888
104891
  }
103889
104892
  function snapshotEnvironmentDocument(environment) {
103890
- const snapshot = structuredClone(environment.baseDocument);
103891
- const values = Array.isArray(snapshot.values) ? snapshot.values : [];
104893
+ const overlay = environment.context.__valueOverlay;
104894
+ const runtime = environment.context.__runtimeSessionValues;
104895
+ const bindings = new Map([
104896
+ ...environment.context.__saveStaticBindings ?? [],
104897
+ ...environment.context.__sessionStaticBindings ?? []
104898
+ ]);
104899
+ if (!environment.hasStateChanges) return environment.baseDocument;
104900
+ const snapshot = { ...environment.baseDocument };
104901
+ const baseValues = Array.isArray(snapshot.values) ? snapshot.values : [];
104902
+ const values = [...baseValues];
103892
104903
  const byId = /* @__PURE__ */ new Map();
103893
104904
  values.forEach((value, index) => {
103894
104905
  if (isRecord10(value) && typeof value.id === "string")
@@ -103905,20 +104916,17 @@ function snapshotEnvironmentDocument(environment) {
103905
104916
  values[index] = cloned;
103906
104917
  }
103907
104918
  };
103908
- for (const row of environment.context.__valueOverlay?.values() ?? [])
103909
- mergeRow(row);
103910
- for (const row of environment.context.__runtimeSessionValues?.values() ?? [])
103911
- mergeRow(row);
104919
+ for (const row of overlay?.values() ?? []) mergeRow(row);
104920
+ for (const row of runtime?.values() ?? []) mergeRow(row);
103912
104921
  snapshot.values = values;
103913
- const members = Array.isArray(snapshot.members) ? snapshot.members : [];
103914
- const bindings = new Map([
103915
- ...environment.context.__saveStaticBindings ?? [],
103916
- ...environment.context.__sessionStaticBindings ?? []
103917
- ]);
103918
- for (const member of members) {
103919
- if (!isRecord10(member) || typeof member.id !== "string") continue;
103920
- if (bindings.has(member.id))
103921
- member.valueId = bindings.get(member.id) ?? null;
104922
+ if (bindings.size > 0) {
104923
+ const baseMembers = Array.isArray(snapshot.members) ? snapshot.members : [];
104924
+ snapshot.members = baseMembers.map((member) => {
104925
+ if (!isRecord10(member) || typeof member.id !== "string" || !bindings.has(member.id)) {
104926
+ return member;
104927
+ }
104928
+ return { ...member, valueId: bindings.get(member.id) ?? null };
104929
+ });
103922
104930
  }
103923
104931
  return snapshot;
103924
104932
  }
@@ -103939,10 +104947,7 @@ function copyMocks(source, target) {
103939
104947
  }
103940
104948
  }
103941
104949
  function cloneTestEnvironment(source) {
103942
- const cloned = createTestEnvironment(
103943
- snapshotEnvironmentDocument(source),
103944
- true
103945
- );
104950
+ const cloned = createTestEnvironment(snapshotEnvironmentDocument(source));
103946
104951
  copyMocks(source, cloned);
103947
104952
  return cloned;
103948
104953
  }
@@ -103953,10 +104958,14 @@ function runCallback(environment, delegate, deadlineMs) {
103953
104958
  __indexes: void 0,
103954
104959
  wallClockDeadlineMs: deadlineMs
103955
104960
  });
103956
- evaluateNSDelegate(
103957
- bindNeoScriptDelegateToContext(delegate, environment.context),
103958
- environment.context
103959
- );
104961
+ try {
104962
+ evaluateNSDelegate(
104963
+ bindNeoScriptDelegateToContext(delegate, environment.context),
104964
+ environment.context
104965
+ );
104966
+ } finally {
104967
+ environment.hasStateChanges ||= (environment.context.__executionState?.writes.length ?? 0) > 0 || (environment.context.__runtimeSessionValues?.size ?? 0) > 0 || (environment.context.__saveStaticBindings?.size ?? 0) > 0 || (environment.context.__sessionStaticBindings?.size ?? 0) > 0;
104968
+ }
103960
104969
  }
103961
104970
  function failureFor(error, file, position, member = null) {
103962
104971
  const timeout = error instanceof NeoScriptWallClockTimeoutError;
@@ -103982,18 +104991,22 @@ function suiteHasSelectedTests(suite, selected2) {
103982
104991
  async function executeRegisteredSpec(registered, document, selected2, timeoutMs, interrupted) {
103983
104992
  const results = [];
103984
104993
  const fileFailures = [];
104994
+ let testDurationMs = 0;
103985
104995
  const executeSuite = async (suite, parentEnvironment, inheritedFailures) => {
103986
104996
  if (!suiteHasSelectedTests(suite, selected2)) return;
103987
104997
  const fixture = cloneTestEnvironment(parentEnvironment);
103988
104998
  const suiteFailures = [...inheritedFailures];
103989
104999
  const suiteDeadline = Date.now() + timeoutMs;
103990
105000
  for (const hook of suite.beforeAll) {
105001
+ const hookStarted = performance.now();
103991
105002
  try {
103992
105003
  runCallback(fixture, hook, suiteDeadline);
103993
105004
  } catch (error) {
103994
105005
  suiteFailures.push(
103995
105006
  failureFor(error, registered.spec.path, suite, "beforeAll")
103996
105007
  );
105008
+ } finally {
105009
+ testDurationMs += performance.now() - hookStarted;
103997
105010
  }
103998
105011
  }
103999
105012
  for (const test of suite.tests) {
@@ -104001,6 +105014,8 @@ async function executeRegisteredSpec(registered, document, selected2, timeoutMs,
104001
105014
  const started = performance.now();
104002
105015
  const deadline = Date.now() + timeoutMs;
104003
105016
  const environment = cloneTestEnvironment(fixture);
105017
+ const startupBeforeTestMs = performance.now() - started;
105018
+ const testStarted = performance.now();
104004
105019
  const failures = [...suiteFailures];
104005
105020
  let setupFailed = failures.length > 0;
104006
105021
  if (!setupFailed) {
@@ -104037,19 +105052,24 @@ async function executeRegisteredSpec(registered, document, selected2, timeoutMs,
104037
105052
  }
104038
105053
  }
104039
105054
  }
104040
- const durationMs = performance.now() - started;
105055
+ const currentTestDurationMs = performance.now() - testStarted;
105056
+ const schedulerStarted = performance.now();
105057
+ await new Promise((resolvePromise) => {
105058
+ setImmediate(resolvePromise);
105059
+ });
105060
+ const startupDurationMs = startupBeforeTestMs + performance.now() - schedulerStarted;
105061
+ testDurationMs += currentTestDurationMs;
104041
105062
  results.push({
104042
105063
  id: test.id,
104043
105064
  file: registered.spec.path,
104044
105065
  name: test.name,
104045
105066
  fullName: test.fullName,
104046
105067
  status: failures.length === 0 ? "passed" : "failed",
104047
- durationMs,
105068
+ durationMs: startupDurationMs + currentTestDurationMs,
105069
+ startupDurationMs,
105070
+ testDurationMs: currentTestDurationMs,
104048
105071
  failures
104049
105072
  });
104050
- await new Promise((resolvePromise) => {
104051
- setImmediate(resolvePromise);
104052
- });
104053
105073
  if (interrupted()) break;
104054
105074
  }
104055
105075
  for (const child of suite.suites) {
@@ -104057,12 +105077,15 @@ async function executeRegisteredSpec(registered, document, selected2, timeoutMs,
104057
105077
  await executeSuite(child, fixture, suiteFailures);
104058
105078
  }
104059
105079
  for (const hook of suite.afterAll) {
105080
+ const hookStarted = performance.now();
104060
105081
  try {
104061
105082
  runCallback(fixture, hook, Date.now() + timeoutMs);
104062
105083
  } catch (error) {
104063
105084
  fileFailures.push(
104064
105085
  failureFor(error, registered.spec.path, suite, "afterAll")
104065
105086
  );
105087
+ } finally {
105088
+ testDurationMs += performance.now() - hookStarted;
104066
105089
  }
104067
105090
  }
104068
105091
  };
@@ -104073,7 +105096,7 @@ async function executeRegisteredSpec(registered, document, selected2, timeoutMs,
104073
105096
  results.sort(
104074
105097
  (left, right) => (registrationOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (registrationOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER)
104075
105098
  );
104076
- return { tests: results, failures: fileFailures };
105099
+ return { tests: results, failures: fileFailures, testDurationMs };
104077
105100
  }
104078
105101
  function atomicWrite(path, content) {
104079
105102
  mkdirSync11(dirname9(path), { recursive: true });
@@ -104126,8 +105149,28 @@ function maintainNeoTestBuildCache(root, maxBytes = TEST_BUILD_CACHE_LIMIT_BYTES
104126
105149
  total -= file.size;
104127
105150
  }
104128
105151
  }
105152
+ function formatTestDuration(durationMs) {
105153
+ if (durationMs >= 1e3) {
105154
+ const seconds = durationMs / 1e3;
105155
+ return `${seconds < 10 ? seconds.toFixed(2) : seconds.toFixed(1)}s`;
105156
+ }
105157
+ return `${durationMs < 10 ? durationMs.toFixed(2) : durationMs.toFixed(1)}ms`;
105158
+ }
105159
+ function paintTestDuration(durationMs) {
105160
+ const formatted = formatTestDuration(durationMs);
105161
+ return durationMs >= 300 ? color.yellow(formatted) : color.dim(formatted);
105162
+ }
105163
+ function formatStatusCounts(passed, failed, total, skipped = 0) {
105164
+ const parts = [
105165
+ failed > 0 ? color.red(`${failed} failed`) : null,
105166
+ passed > 0 ? color.green(`${passed} passed`) : null,
105167
+ skipped > 0 ? color.yellow(`${skipped} skipped`) : null
105168
+ ].filter((part) => part !== null);
105169
+ return `${parts.join(color.dim(" | "))} ${color.dim(`(${total})`)}`;
105170
+ }
104129
105171
  async function runTest(workspace, options, dependencies = {}) {
104130
105172
  const started = Date.now();
105173
+ const performanceStarted = performance.now();
104131
105174
  const testBuildRoot = join16(workspace.root, ".neo", "test-build");
104132
105175
  maintainNeoTestBuildCache(testBuildRoot);
104133
105176
  const startedAt = new Date(started).toISOString();
@@ -104140,6 +105183,7 @@ async function runTest(workspace, options, dependencies = {}) {
104140
105183
  const selectedTestCountByFile = /* @__PURE__ */ new Map();
104141
105184
  const fileFailuresByPath = /* @__PURE__ */ new Map();
104142
105185
  const diagnostics = [];
105186
+ let testDurationMs = 0;
104143
105187
  let exitCode = 0;
104144
105188
  let interrupted = false;
104145
105189
  const handleSigint = () => {
@@ -104153,22 +105197,77 @@ async function runTest(workspace, options, dependencies = {}) {
104153
105197
  }
104154
105198
  };
104155
105199
  process.on("SIGINT", handleSigint);
105200
+ const phaseDurations = {
105201
+ prepare: 0,
105202
+ select: 0,
105203
+ compile: 0,
105204
+ register: 0,
105205
+ execute: 0,
105206
+ finalize: 0
105207
+ };
104156
105208
  let phase = "prepare";
105209
+ let phaseStarted = performanceStarted;
105210
+ const enterPhase = (next, label) => {
105211
+ const finished = performance.now();
105212
+ phaseDurations[phase] += finished - phaseStarted;
105213
+ phase = next;
105214
+ phaseStarted = finished;
105215
+ dependencies.progress?.update(label);
105216
+ };
105217
+ const finishPhase = () => {
105218
+ const finished = performance.now();
105219
+ phaseDurations[phase] += finished - phaseStarted;
105220
+ phaseStarted = finished;
105221
+ };
105222
+ dependencies.progress?.update("Preparing test project\u2026");
104157
105223
  try {
104158
- const candidate = dependencies.prepareLocalCandidate === void 0 ? preparedHookCandidate(workspace) ?? await prepareLocalCandidateV4(workspace) : await dependencies.prepareLocalCandidate(workspace);
105224
+ let candidate = dependencies.prepareLocalCandidate === void 0 ? preparedHookCandidate(workspace) : null;
105225
+ let candidateInputFingerprint = null;
105226
+ let shouldCacheCandidate = false;
105227
+ if (candidate === null) {
105228
+ const canUseCandidateCache = dependencies.prepareLocalCandidate === void 0 || dependencies.fingerprintCandidateInputs !== void 0;
105229
+ if (canUseCandidateCache) {
105230
+ candidateInputFingerprint = dependencies.fingerprintCandidateInputs?.(workspace) ?? fingerprintTestCandidateInputs(workspace);
105231
+ candidate = cachedTestCandidate(workspace, candidateInputFingerprint);
105232
+ }
105233
+ if (candidate === null) {
105234
+ const prepared = dependencies.prepareLocalCandidate === void 0 ? await prepareLocalCandidateV4(workspace, {
105235
+ onPhase: (label) => {
105236
+ dependencies.progress?.update(label);
105237
+ }
105238
+ }) : await dependencies.prepareLocalCandidate(workspace);
105239
+ if (prepared.document === null) {
105240
+ throw new NeoTestReportWriteError(
105241
+ "Local test preparation did not produce a project document."
105242
+ );
105243
+ }
105244
+ candidate = {
105245
+ document: prepared.document,
105246
+ sourceHash: prepared.sourceHash
105247
+ };
105248
+ shouldCacheCandidate = canUseCandidateCache;
105249
+ }
105250
+ }
104159
105251
  assertNotInterrupted();
104160
- if (candidate.document === null) {
105252
+ if (candidate === null) {
104161
105253
  throw new NeoTestReportWriteError(
104162
- "Local test preparation did not produce a project document."
105254
+ "Local test preparation produced no reusable candidate."
104163
105255
  );
104164
105256
  }
105257
+ if (shouldCacheCandidate && candidateInputFingerprint !== null) {
105258
+ candidate = cacheTestCandidate(workspace, candidateInputFingerprint, {
105259
+ document: candidate.document,
105260
+ sourceHash: candidate.sourceHash
105261
+ });
105262
+ }
104165
105263
  projectFingerprint = `sha256:${candidate.sourceHash}`;
104166
105264
  const rawDocument = documentRecord(candidate.document);
104167
105265
  const compilationHash = projectCompilationHash(
104168
105266
  candidate.sourceHash,
104169
- rawDocument
105267
+ rawDocument,
105268
+ candidate.documentSha256
104170
105269
  );
104171
- phase = "select";
105270
+ enterPhase("select", "Selecting test files\u2026");
104172
105271
  let selectedPaths;
104173
105272
  try {
104174
105273
  selectedPaths = selectedSpecPaths(workspace, options.selectors);
@@ -104183,7 +105282,7 @@ async function runTest(workspace, options, dependencies = {}) {
104183
105282
  "No .spec.neo files matched. Pass --passWithNoTests to treat this as success."
104184
105283
  );
104185
105284
  }
104186
- phase = "compile";
105285
+ enterPhase("compile", "Compiling test files\u2026");
104187
105286
  specs = selectedPaths.map(
104188
105287
  (path) => compileSpec(workspace, rawDocument, path, compilationHash)
104189
105288
  );
@@ -104197,7 +105296,7 @@ async function runTest(workspace, options, dependencies = {}) {
104197
105296
  );
104198
105297
  }
104199
105298
  }
104200
- phase = "register";
105299
+ enterPhase("register", "Registering tests\u2026");
104201
105300
  const registered = specs.map((spec) => registerSpec(spec, rawDocument));
104202
105301
  const selectedTests = registered.flatMap((entry) => entry.tests).filter((test) => pattern === null || pattern.test(test.fullName));
104203
105302
  for (const entry of registered) {
@@ -104214,7 +105313,7 @@ async function runTest(workspace, options, dependencies = {}) {
104214
105313
  );
104215
105314
  }
104216
105315
  const selectedIds = new Set(selectedTests.map((test) => test.id));
104217
- phase = "execute";
105316
+ enterPhase("execute", "Running tests\u2026");
104218
105317
  const timeoutMs = options.timeoutMs ?? workspace.config.test?.timeoutMs ?? 5e3;
104219
105318
  for (const entry of registered) {
104220
105319
  const executed = await executeRegisteredSpec(
@@ -104225,12 +105324,14 @@ async function runTest(workspace, options, dependencies = {}) {
104225
105324
  () => interrupted
104226
105325
  );
104227
105326
  results.push(...executed.tests);
105327
+ testDurationMs += executed.testDurationMs;
104228
105328
  fileFailuresByPath.set(entry.spec.path, executed.failures);
104229
105329
  assertNotInterrupted();
104230
105330
  }
104231
105331
  if (results.some((result) => result.status === "failed") || [...fileFailuresByPath.values()].some((failures) => failures.length > 0)) {
104232
105332
  exitCode = 1;
104233
105333
  }
105334
+ enterPhase("finalize", "Finalizing test report\u2026");
104234
105335
  const buildManifest = {
104235
105336
  version: 1,
104236
105337
  cliVersion: PROJECT_SCHEMA_CONTRACT.cliVersion,
@@ -104238,14 +105339,14 @@ async function runTest(workspace, options, dependencies = {}) {
104238
105339
  compilerRevision: NEOSCRIPT_COMPILER_REVISION,
104239
105340
  evaluatorRevision: 4,
104240
105341
  projectFingerprint,
104241
- configurationSha256: createHash10("sha256").update(JSON.stringify(workspace.config.test ?? {})).digest("hex"),
105342
+ configurationSha256: createHash11("sha256").update(JSON.stringify(workspace.config.test ?? {})).digest("hex"),
104242
105343
  dependencyGraph: Object.fromEntries(
104243
105344
  specs.map((spec) => [spec.path, [projectFingerprint]])
104244
105345
  ),
104245
105346
  artifacts: specs.map((spec) => ({
104246
105347
  path: spec.path,
104247
- sourceSha256: createHash10("sha256").update(spec.source).digest("hex"),
104248
- artifactSha256: createHash10("sha256").update(JSON.stringify(spec.action)).digest("hex")
105348
+ sourceSha256: createHash11("sha256").update(spec.source).digest("hex"),
105349
+ artifactSha256: createHash11("sha256").update(JSON.stringify(spec.action)).digest("hex")
104249
105350
  }))
104250
105351
  };
104251
105352
  atomicWrite(
@@ -104266,6 +105367,8 @@ async function runTest(workspace, options, dependencies = {}) {
104266
105367
  related: []
104267
105368
  });
104268
105369
  }
105370
+ finishPhase();
105371
+ dependencies.progress?.stop();
104269
105372
  process.off("SIGINT", handleSigint);
104270
105373
  const failed = results.filter((result) => result.status === "failed").length;
104271
105374
  const selectedTestTotal = [...selectedTestCountByFile.values()].reduce(
@@ -104273,7 +105376,16 @@ async function runTest(workspace, options, dependencies = {}) {
104273
105376
  0
104274
105377
  );
104275
105378
  const skipped = Math.max(0, selectedTestTotal - results.length);
104276
- const durationMs = Date.now() - started;
105379
+ const durationMs = performance.now() - performanceStarted;
105380
+ const startupDurationMs = Math.max(0, durationMs - testDurationMs);
105381
+ const perTestStartupDurationMs = results.reduce(
105382
+ (sum, result) => sum + result.startupDurationMs,
105383
+ 0
105384
+ );
105385
+ const sharedStartupDurationMs = Math.max(
105386
+ 0,
105387
+ startupDurationMs - perTestStartupDurationMs
105388
+ );
104277
105389
  const testFiles = specs.map((spec) => {
104278
105390
  const tests = results.filter((result) => result.file === spec.path);
104279
105391
  const failures = fileFailuresByPath.get(spec.path) ?? [];
@@ -104290,7 +105402,7 @@ async function runTest(workspace, options, dependencies = {}) {
104290
105402
  success: exitCode === 0,
104291
105403
  projectFingerprint,
104292
105404
  seed: Number.parseInt(
104293
- createHash10("sha256").update(projectFingerprint ?? "neo-test:no-project").digest("hex").slice(0, 8),
105405
+ createHash11("sha256").update(projectFingerprint ?? "neo-test:no-project").digest("hex").slice(0, 8),
104294
105406
  16
104295
105407
  ),
104296
105408
  selection: {
@@ -104305,7 +105417,18 @@ async function runTest(workspace, options, dependencies = {}) {
104305
105417
  failed,
104306
105418
  skipped,
104307
105419
  todo: 0,
104308
- durationMs
105420
+ durationMs,
105421
+ startupDurationMs,
105422
+ sharedStartupDurationMs,
105423
+ testDurationMs
105424
+ },
105425
+ timings: {
105426
+ prepareMs: phaseDurations.prepare,
105427
+ selectMs: phaseDurations.select,
105428
+ compileMs: phaseDurations.compile,
105429
+ registerMs: phaseDurations.register,
105430
+ executeMs: phaseDurations.execute,
105431
+ finalizeMs: phaseDurations.finalize
104309
105432
  },
104310
105433
  testFiles,
104311
105434
  diagnostics,
@@ -104330,30 +105453,63 @@ async function runTest(workspace, options, dependencies = {}) {
104330
105453
  if (options.reporter === "json") {
104331
105454
  if (options.outputFile === null) process.stdout.write(serialized);
104332
105455
  } else {
104333
- for (const result of results) {
105456
+ console.log("");
105457
+ console.log(
105458
+ ` ${color.bold("RUN")} ${color.dim(`neo ${PROJECT_SCHEMA_CONTRACT.cliVersion} ${workspace.root}`)}`
105459
+ );
105460
+ console.log("");
105461
+ console.log(
105462
+ ` ${color.cyan("\u21BB")} ${color.dim("shared startup")} ${paintTestDuration(report.summary.sharedStartupDurationMs)}`
105463
+ );
105464
+ for (const file of testFiles) {
105465
+ const fileDurationMs = file.tests.reduce(
105466
+ (sum, test) => sum + test.durationMs,
105467
+ 0
105468
+ );
105469
+ const fileSymbol = file.status === "passed" ? color.green("\u2713") : file.status === "failed" ? color.red("\xD7") : color.yellow("!");
105470
+ const testLabel = `${file.tests.length} ${file.tests.length === 1 ? "test" : "tests"}`;
104334
105471
  console.log(
104335
- `${result.status === "passed" ? "\u2713" : "\u2717"} ${result.file} > ${result.name} (${result.durationMs.toFixed(1)}ms)`
105472
+ ` ${fileSymbol} ${color.bold(file.path)} ${color.dim(`(${testLabel})`)} ${paintTestDuration(fileDurationMs)}`
104336
105473
  );
104337
- for (const failure of result.failures)
104338
- console.log(` ${failure.message}`);
104339
- }
104340
- for (const file of testFiles) {
105474
+ for (const result of file.tests) {
105475
+ const resultSymbol = result.status === "passed" ? color.green("\u2713") : color.red("\xD7");
105476
+ console.log(
105477
+ ` ${resultSymbol} ${result.name} ${color.dim("total")} ${paintTestDuration(result.durationMs)} ${color.dim(`(test ${formatTestDuration(result.testDurationMs)}, startup ${formatTestDuration(result.startupDurationMs)})`)}`
105478
+ );
105479
+ for (const failure of result.failures) {
105480
+ console.log(` ${color.red(failure.message)}`);
105481
+ }
105482
+ }
104341
105483
  if (file.status === "interrupted") {
104342
105484
  console.log(
104343
- `! ${file.path} > interrupted before all selected tests ran`
105485
+ ` ${color.yellow("!")} interrupted before all selected tests ran`
104344
105486
  );
104345
105487
  }
104346
105488
  for (const failure of file.failures) {
104347
105489
  console.log(
104348
- `\u2717 ${file.path} > ${failure.frames[0]?.member ?? "suite hook"}`
105490
+ ` ${color.red("\xD7")} ${failure.frames[0]?.member ?? "suite hook"}`
104349
105491
  );
104350
- console.log(` ${failure.message}`);
105492
+ console.log(` ${color.red(failure.message)}`);
104351
105493
  }
104352
105494
  }
104353
- for (const diagnostic of diagnostics)
104354
- console.log(`\u2717 ${diagnostic.message}`);
105495
+ for (const diagnostic of diagnostics) {
105496
+ console.log(` ${color.red("\xD7")} ${diagnostic.message}`);
105497
+ }
105498
+ console.log("");
105499
+ const passedFiles = testFiles.filter(
105500
+ (file) => file.status === "passed"
105501
+ ).length;
105502
+ const failedFiles = testFiles.filter(
105503
+ (file) => file.status === "failed"
105504
+ ).length;
105505
+ console.log(
105506
+ ` ${color.bold(pad("Test Files", 11))} ${formatStatusCounts(passedFiles, failedFiles, report.summary.files)}`
105507
+ );
104355
105508
  console.log(
104356
- `${report.summary.passed} passed, ${report.summary.failed} failed (${report.summary.durationMs}ms)`
105509
+ ` ${color.bold(pad("Tests", 11))} ${formatStatusCounts(report.summary.passed, report.summary.failed, report.summary.tests, report.summary.skipped)}`
105510
+ );
105511
+ console.log(
105512
+ ` ${color.bold(pad("Duration", 11))} ${color.bold(`total ${formatTestDuration(report.summary.durationMs)}`)} ${color.dim(`(test ${formatTestDuration(report.summary.testDurationMs)}, startup ${formatTestDuration(report.summary.startupDurationMs)})`)}`
104357
105513
  );
104358
105514
  }
104359
105515
  if (!report.success) process.exitCode = exitCode;
@@ -104400,7 +105556,7 @@ async function inspectNeoTestCompilation(workspace, dependencies = {}) {
104400
105556
  errors
104401
105557
  };
104402
105558
  }
104403
- var TEST_BUILD_CACHE_LIMIT_BYTES, ABANDONED_TEMP_MAX_AGE_MS, NeoTestUsageError, NeoTestNoTestsError, NeoTestRegistrationError, NeoTestReportWriteError, NeoTestPreparedCandidateError, NeoTestInterruptedError, NeoTestAssertionError, ERROR_SOURCE_POSITIONS, REGISTRATION_IDS;
105559
+ var TEST_BUILD_CACHE_LIMIT_BYTES, ABANDONED_TEMP_MAX_AGE_MS, TEST_CANDIDATE_CACHE_REVISION, NeoTestUsageError, NeoTestNoTestsError, NeoTestRegistrationError, NeoTestReportWriteError, NeoTestPreparedCandidateError, NeoTestInterruptedError, NeoTestAssertionError, ERROR_SOURCE_POSITIONS, SHARED_EVALUATOR_BASES, REGISTRATION_IDS, MATCHER_IDS;
104404
105560
  var init_test = __esm({
104405
105561
  "src/commands/test.ts"() {
104406
105562
  "use strict";
@@ -104413,8 +105569,10 @@ var init_test = __esm({
104413
105569
  init_push();
104414
105570
  init_registry2();
104415
105571
  init_push_hook();
105572
+ init_ui();
104416
105573
  TEST_BUILD_CACHE_LIMIT_BYTES = 512 * 1024 * 1024;
104417
105574
  ABANDONED_TEMP_MAX_AGE_MS = 60 * 60 * 1e3;
105575
+ TEST_CANDIDATE_CACHE_REVISION = 1;
104418
105576
  NeoTestUsageError = class extends Error {
104419
105577
  name = "NeoTestUsageError";
104420
105578
  };
@@ -104446,6 +105604,7 @@ var init_test = __esm({
104446
105604
  received;
104447
105605
  };
104448
105606
  ERROR_SOURCE_POSITIONS = /* @__PURE__ */ new WeakMap();
105607
+ SHARED_EVALUATOR_BASES = /* @__PURE__ */ new WeakMap();
104449
105608
  REGISTRATION_IDS = /* @__PURE__ */ new Set([
104450
105609
  NEO_TEST_IDS.describe,
104451
105610
  NEO_TEST_IDS.test,
@@ -104455,6 +105614,24 @@ var init_test = __esm({
104455
105614
  NEO_TEST_IDS.beforeEach,
104456
105615
  NEO_TEST_IDS.afterEach
104457
105616
  ]);
105617
+ MATCHER_IDS = /* @__PURE__ */ new Set([
105618
+ NEO_TEST_IDS.toBe,
105619
+ NEO_TEST_IDS.toEqual,
105620
+ NEO_TEST_IDS.toBeNull,
105621
+ NEO_TEST_IDS.toBeTrue,
105622
+ NEO_TEST_IDS.toBeFalse,
105623
+ NEO_TEST_IDS.toBeGreaterThan,
105624
+ NEO_TEST_IDS.toBeGreaterThanOrEqual,
105625
+ NEO_TEST_IDS.toBeLessThan,
105626
+ NEO_TEST_IDS.toBeLessThanOrEqual,
105627
+ NEO_TEST_IDS.toContain,
105628
+ NEO_TEST_IDS.toHaveLength,
105629
+ NEO_TEST_IDS.toMatchObject,
105630
+ NEO_TEST_IDS.toThrow,
105631
+ NEO_TEST_IDS.toHaveBeenCalled,
105632
+ NEO_TEST_IDS.toHaveBeenCalledTimes,
105633
+ NEO_TEST_IDS.toHaveBeenCalledWith
105634
+ ]);
104458
105635
  }
104459
105636
  });
104460
105637
 
@@ -107841,7 +109018,8 @@ function loadWorkspaceForCommand(args) {
107841
109018
  // A reset reconstructs the working copy from the server and may therefore
107842
109019
  // discard an unsupported pre-cutover cache. No other command gets this
107843
109020
  // exception, so format 2 never becomes an active compatibility read path.
107844
- discardLegacyFormat2State: args.command === "pull" && boolFlag(args, "reset")
109021
+ discardLegacyFormat2State: args.command === "pull" && boolFlag(args, "reset"),
109022
+ fingerprintStateSource: args.command === "test"
107845
109023
  });
107846
109024
  const apiOverride = stringFlag(args, "api");
107847
109025
  if (apiOverride !== null) {
@@ -108019,7 +109197,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
108019
109197
  async function main() {
108020
109198
  const args = parseArgs(process.argv.slice(2));
108021
109199
  if (args.command === "--version") {
108022
- console.log("0.26.1");
109200
+ console.log("0.26.3");
108023
109201
  return;
108024
109202
  }
108025
109203
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -108303,15 +109481,25 @@ async function main() {
108303
109481
  "--testTimeout must be a positive number of milliseconds."
108304
109482
  );
108305
109483
  }
108306
- const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
108307
- await runTest2(workspace, {
108308
- selectors: args.positional,
108309
- testNamePattern: stringFlag(args, "testNamePattern"),
108310
- reporter: reporterValue,
108311
- outputFile: stringFlag(args, "outputFile") ?? stringFlag(args, "output-file"),
108312
- passWithNoTests: boolFlag(args, "passWithNoTests"),
108313
- ...timeoutMs === void 0 ? {} : { timeoutMs }
108314
- });
109484
+ const progress = reporterValue === "default" && process.stdout.isTTY === true ? spinner("Preparing test project\u2026") : null;
109485
+ try {
109486
+ const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
109487
+ await runTest2(
109488
+ workspace,
109489
+ {
109490
+ selectors: args.positional,
109491
+ testNamePattern: stringFlag(args, "testNamePattern"),
109492
+ reporter: reporterValue,
109493
+ outputFile: stringFlag(args, "outputFile") ?? stringFlag(args, "output-file"),
109494
+ passWithNoTests: boolFlag(args, "passWithNoTests"),
109495
+ ...timeoutMs === void 0 ? {} : { timeoutMs }
109496
+ },
109497
+ progress === null ? {} : { progress }
109498
+ );
109499
+ } catch (error) {
109500
+ progress?.fail("Test run failed.");
109501
+ throw error;
109502
+ }
108315
109503
  return;
108316
109504
  }
108317
109505
  case "dev": {