vitest 3.2.0-beta.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE.md +29 -0
  2. package/dist/browser.d.ts +3 -3
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/{base.DwtwORaC.js → base.Cg0miDlQ.js} +11 -14
  5. package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.CYdenmiT.js} +4 -6
  6. package/dist/chunks/{cac.I9MLYfT-.js → cac.6rXCxFY1.js} +76 -143
  7. package/dist/chunks/{cli-api.d6IK1pnk.js → cli-api.Cej3MBjA.js} +1460 -1344
  8. package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
  9. package/dist/chunks/{console.K1NMVOSc.js → console.CtFJOzRO.js} +25 -45
  10. package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
  11. package/dist/chunks/{coverage.0iPg4Wrz.js → coverage.DVF1vEu8.js} +4 -12
  12. package/dist/chunks/{coverage.OGU09Jbh.js → coverage.EIiagJJP.js} +578 -993
  13. package/dist/chunks/{creator.DGAdZ4Hj.js → creator.GK6I-cL4.js} +39 -83
  14. package/dist/chunks/date.Bq6ZW5rf.js +73 -0
  15. package/dist/chunks/{defaults.DSxsTG0h.js → defaults.B7q_naMc.js} +2 -1
  16. package/dist/chunks/{env.Dq0hM4Xv.js → env.D4Lgay0q.js} +1 -1
  17. package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
  18. package/dist/chunks/{execute.JlGHLJZT.js → execute.B7h3T_Hc.js} +126 -217
  19. package/dist/chunks/{git.DXfdBEfR.js → git.BVQ8w_Sw.js} +1 -3
  20. package/dist/chunks/{global.d.BPa1eL3O.d.ts → global.d.MAmajcmJ.d.ts} +5 -1
  21. package/dist/chunks/{globals.CpxW8ccg.js → globals.DEHgCU4V.js} +7 -6
  22. package/dist/chunks/{index.CV36oG_L.js → index.BZ0g1JD2.js} +430 -625
  23. package/dist/chunks/{index.DswW_LEs.js → index.BbB8_kAK.js} +25 -24
  24. package/dist/chunks/{index.CmC5OK9L.js → index.CIyJn3t1.js} +38 -82
  25. package/dist/chunks/{index.CfXMNXHg.js → index.CdQS2e2Q.js} +4 -2
  26. package/dist/chunks/{index.DFXFpH3w.js → index.CmSc2RE5.js} +85 -105
  27. package/dist/chunks/index.D3XRDfWc.js +213 -0
  28. package/dist/chunks/{inspector.DbDkSkFn.js → inspector.C914Efll.js} +4 -1
  29. package/dist/chunks/{node.3xsWotC9.js → node.fjCdwEIl.js} +1 -1
  30. package/dist/chunks/{reporters.d.CLC9rhKy.d.ts → reporters.d.C1ogPriE.d.ts} +47 -9
  31. package/dist/chunks/{rpc.D9_013TY.js → rpc.Iovn4oWe.js} +10 -19
  32. package/dist/chunks/{runBaseTests.Dn2vyej_.js → runBaseTests.Dd85QTll.js} +27 -31
  33. package/dist/chunks/{setup-common.CYo3Y0dD.js → setup-common.Dd054P77.js} +16 -42
  34. package/dist/chunks/{typechecker.DnTrplSJ.js → typechecker.DRKU1-1g.js} +163 -186
  35. package/dist/chunks/{utils.BfxieIyZ.js → utils.CAioKnHs.js} +9 -14
  36. package/dist/chunks/{utils.CgTj3MsC.js → utils.XdZDrNZV.js} +6 -13
  37. package/dist/chunks/{vi.BFR5YIgu.js → vi.bdSIJ99Y.js} +137 -263
  38. package/dist/chunks/{vite.d.CBZ3M_ru.d.ts → vite.d.DqE4-hhK.d.ts} +3 -1
  39. package/dist/chunks/{vm.C1HHjtNS.js → vm.BThCzidc.js} +164 -212
  40. package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.DvqK5Vmu.d.ts} +1 -1
  41. package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.tQu2eJQy.d.ts} +5 -3
  42. package/dist/cli.js +5 -5
  43. package/dist/config.cjs +3 -1
  44. package/dist/config.d.ts +7 -6
  45. package/dist/config.js +3 -3
  46. package/dist/coverage.d.ts +4 -4
  47. package/dist/coverage.js +7 -7
  48. package/dist/environments.d.ts +6 -2
  49. package/dist/environments.js +1 -1
  50. package/dist/execute.d.ts +9 -3
  51. package/dist/execute.js +1 -1
  52. package/dist/index.d.ts +28 -15
  53. package/dist/index.js +5 -5
  54. package/dist/node.d.ts +18 -10
  55. package/dist/node.js +17 -17
  56. package/dist/reporters.d.ts +4 -4
  57. package/dist/reporters.js +4 -4
  58. package/dist/runners.d.ts +6 -3
  59. package/dist/runners.js +59 -80
  60. package/dist/snapshot.js +2 -2
  61. package/dist/suite.js +2 -2
  62. package/dist/worker.js +39 -41
  63. package/dist/workers/forks.js +6 -4
  64. package/dist/workers/runVmTests.js +20 -21
  65. package/dist/workers/threads.js +4 -4
  66. package/dist/workers/vmForks.js +6 -6
  67. package/dist/workers/vmThreads.js +6 -6
  68. package/dist/workers.d.ts +4 -4
  69. package/dist/workers.js +10 -10
  70. package/package.json +21 -19
  71. package/dist/chunks/date.CDOsz-HY.js +0 -53
  72. package/dist/chunks/index.CK1YOQaa.js +0 -143
@@ -23,11 +23,14 @@ function convertTasksToEvents(file, onTask) {
23
23
  suite.result,
24
24
  suite.meta
25
25
  ]);
26
- events.push([suite.id, "suite-prepare"]);
26
+ events.push([
27
+ suite.id,
28
+ "suite-prepare",
29
+ void 0
30
+ ]);
27
31
  suite.tasks.forEach((task) => {
28
- if (task.type === "suite") {
29
- visit(task);
30
- } else {
32
+ if (task.type === "suite") visit(task);
33
+ else {
31
34
  onTask?.(task);
32
35
  if (suite.mode !== "skip" && suite.mode !== "todo") {
33
36
  packs.push([
@@ -35,11 +38,31 @@ function convertTasksToEvents(file, onTask) {
35
38
  task.result,
36
39
  task.meta
37
40
  ]);
38
- events.push([task.id, "test-prepare"], [task.id, "test-finished"]);
41
+ events.push([
42
+ task.id,
43
+ "test-prepare",
44
+ void 0
45
+ ]);
46
+ task.annotations.forEach((annotation) => {
47
+ events.push([
48
+ task.id,
49
+ "test-annotation",
50
+ { annotation }
51
+ ]);
52
+ });
53
+ events.push([
54
+ task.id,
55
+ "test-finished",
56
+ void 0
57
+ ]);
39
58
  }
40
59
  }
41
60
  });
42
- events.push([suite.id, "suite-finished"]);
61
+ events.push([
62
+ suite.id,
63
+ "suite-finished",
64
+ void 0
65
+ ]);
43
66
  }
44
67
  visit(file);
45
68
  return {
@@ -50,12 +73,8 @@ function convertTasksToEvents(file, onTask) {
50
73
 
51
74
  const REGEXP_WRAP_PREFIX = "$$vitest:";
52
75
  function getOutputFile(config, reporter) {
53
- if (!config?.outputFile) {
54
- return;
55
- }
56
- if (typeof config.outputFile === "string") {
57
- return config.outputFile;
58
- }
76
+ if (!config?.outputFile) return;
77
+ if (typeof config.outputFile === "string") return config.outputFile;
59
78
  return config.outputFile[reporter];
60
79
  }
61
80
  /**
@@ -64,15 +83,13 @@ function getOutputFile(config, reporter) {
64
83
  function wrapSerializableConfig(config) {
65
84
  let testNamePattern = config.testNamePattern;
66
85
  let defines = config.defines;
67
- if (testNamePattern && typeof testNamePattern !== "string") {
68
- testNamePattern = `${REGEXP_WRAP_PREFIX}${testNamePattern.toString()}`;
69
- }
70
- if (defines) {
71
- defines = {
72
- keys: Object.keys(defines),
73
- original: defines
74
- };
75
- }
86
+ // v8 serialize does not support regex
87
+ if (testNamePattern && typeof testNamePattern !== "string") testNamePattern = `${REGEXP_WRAP_PREFIX}${testNamePattern.toString()}`;
88
+ // v8 serialize drops properties with undefined value
89
+ if (defines) defines = {
90
+ keys: Object.keys(defines),
91
+ original: defines
92
+ };
76
93
  return {
77
94
  ...config,
78
95
  testNamePattern,
@@ -358,9 +375,7 @@ base.MethodDefinition = base.PropertyDefinition = base.Property = function (node
358
375
 
359
376
  async function collectTests(ctx, filepath) {
360
377
  const request = await ctx.vitenode.transformRequest(filepath, filepath);
361
- if (!request) {
362
- return null;
363
- }
378
+ if (!request) return null;
364
379
  const ast = await parseAstAsync(request.code);
365
380
  const testFilepath = relative(ctx.config.root, filepath);
366
381
  const projectName = ctx.name;
@@ -381,79 +396,59 @@ async function collectTests(ctx, filepath) {
381
396
  file.file = file;
382
397
  const definitions = [];
383
398
  const getName = (callee) => {
384
- if (!callee) {
385
- return null;
386
- }
387
- if (callee.type === "Identifier") {
388
- return callee.name;
389
- }
390
- if (callee.type === "CallExpression") {
391
- return getName(callee.callee);
392
- }
393
- if (callee.type === "TaggedTemplateExpression") {
394
- return getName(callee.tag);
395
- }
399
+ if (!callee) return null;
400
+ if (callee.type === "Identifier") return callee.name;
401
+ if (callee.type === "CallExpression") return getName(callee.callee);
402
+ if (callee.type === "TaggedTemplateExpression") return getName(callee.tag);
396
403
  if (callee.type === "MemberExpression") {
397
404
  if (callee.object?.type === "Identifier" && [
398
405
  "it",
399
406
  "test",
400
407
  "describe",
401
408
  "suite"
402
- ].includes(callee.object.name)) {
403
- return callee.object?.name;
404
- }
405
- if (callee.object?.name?.startsWith("__vite_ssr_")) {
406
- return getName(callee.property);
407
- }
409
+ ].includes(callee.object.name)) return callee.object?.name;
410
+ // direct call as `__vite_ssr_exports_0__.test()`
411
+ if (callee.object?.name?.startsWith("__vite_ssr_")) return getName(callee.property);
412
+ // call as `__vite_ssr__.test.skip()`
408
413
  return getName(callee.object?.property);
409
414
  }
415
+ // unwrap (0, ...)
410
416
  if (callee.type === "SequenceExpression" && callee.expressions.length === 2) {
411
417
  const [e0, e1] = callee.expressions;
412
- if (e0.type === "Literal" && e0.value === 0) {
413
- return getName(e1);
414
- }
418
+ if (e0.type === "Literal" && e0.value === 0) return getName(e1);
415
419
  }
416
420
  return null;
417
421
  };
418
422
  ancestor(ast, { CallExpression(node) {
419
423
  const { callee } = node;
420
424
  const name = getName(callee);
421
- if (!name) {
422
- return;
423
- }
425
+ if (!name) return;
424
426
  if (![
425
427
  "it",
426
428
  "test",
427
429
  "describe",
428
430
  "suite"
429
- ].includes(name)) {
430
- return;
431
- }
431
+ ].includes(name)) return;
432
432
  const property = callee?.property?.name;
433
433
  let mode = !property || property === name ? "run" : property;
434
+ // they will be picked up in the next iteration
434
435
  if ([
435
436
  "each",
436
437
  "for",
437
438
  "skipIf",
438
439
  "runIf"
439
- ].includes(mode)) {
440
- return;
441
- }
440
+ ].includes(mode)) return;
442
441
  let start;
443
442
  const end = node.end;
444
- if (callee.type === "CallExpression") {
445
- start = callee.end;
446
- } else if (callee.type === "TaggedTemplateExpression") {
447
- start = callee.end + 1;
448
- } else {
449
- start = node.start;
450
- }
443
+ // .each
444
+ if (callee.type === "CallExpression") start = callee.end;
445
+ else if (callee.type === "TaggedTemplateExpression") start = callee.end + 1;
446
+ else start = node.start;
451
447
  const { arguments: [messageNode] } = node;
452
448
  const isQuoted = messageNode?.type === "Literal" || messageNode?.type === "TemplateLiteral";
453
449
  const message = isQuoted ? request.code.slice(messageNode.start + 1, messageNode.end - 1) : request.code.slice(messageNode.start, messageNode.end);
454
- if (mode === "skipIf" || mode === "runIf") {
455
- mode = "skip";
456
- }
450
+ // cannot statically analyze, so we always skip it
451
+ if (mode === "skipIf" || mode === "runIf") mode = "skip";
457
452
  definitions.push({
458
453
  start,
459
454
  end,
@@ -465,17 +460,15 @@ async function collectTests(ctx, filepath) {
465
460
  } });
466
461
  let lastSuite = file;
467
462
  const updateLatestSuite = (index) => {
468
- while (lastSuite.suite && lastSuite.end < index) {
469
- lastSuite = lastSuite.suite;
470
- }
463
+ while (lastSuite.suite && lastSuite.end < index) lastSuite = lastSuite.suite;
471
464
  return lastSuite;
472
465
  };
473
466
  definitions.sort((a, b) => a.start - b.start).forEach((definition) => {
474
467
  const latestSuite = updateLatestSuite(definition.start);
475
468
  let mode = definition.mode;
476
- if (latestSuite.mode !== "run") {
477
- mode = latestSuite.mode;
478
- }
469
+ if (latestSuite.mode !== "run")
470
+ // inherit suite mode, if it's set
471
+ mode = latestSuite.mode;
479
472
  if (definition.type === "suite") {
480
473
  const task = {
481
474
  type: definition.type,
@@ -505,6 +498,7 @@ async function collectTests(ctx, filepath) {
505
498
  name: definition.name,
506
499
  end: definition.end,
507
500
  start: definition.start,
501
+ annotations: [],
508
502
  meta: { typecheck: true }
509
503
  };
510
504
  definition.task = task;
@@ -512,7 +506,7 @@ async function collectTests(ctx, filepath) {
512
506
  });
513
507
  calculateSuiteHash(file);
514
508
  const hasOnly = someTasksAreOnly(file);
515
- interpretTaskModes(file, ctx.config.testNamePattern, undefined, hasOnly, false, ctx.config.allowOnly);
509
+ interpretTaskModes(file, ctx.config.testNamePattern, void 0, hasOnly, false, ctx.config.allowOnly);
516
510
  return {
517
511
  file,
518
512
  parsed: request.code,
@@ -526,26 +520,18 @@ const newLineRegExp = /\r?\n/;
526
520
  const errCodeRegExp = /error TS(?<errCode>\d+)/;
527
521
  async function makeTscErrorInfo(errInfo) {
528
522
  const [errFilePathPos = "", ...errMsgRawArr] = errInfo.split(":");
529
- if (!errFilePathPos || errMsgRawArr.length === 0 || errMsgRawArr.join("").length === 0) {
530
- return ["unknown filepath", null];
531
- }
523
+ if (!errFilePathPos || errMsgRawArr.length === 0 || errMsgRawArr.join("").length === 0) return ["unknown filepath", null];
532
524
  const errMsgRaw = errMsgRawArr.join("").trim();
525
+ // get filePath, line, col
533
526
  const [errFilePath, errPos] = errFilePathPos.slice(0, -1).split("(");
534
- if (!errFilePath || !errPos) {
535
- return ["unknown filepath", null];
536
- }
527
+ if (!errFilePath || !errPos) return ["unknown filepath", null];
537
528
  const [errLine, errCol] = errPos.split(",");
538
- if (!errLine || !errCol) {
539
- return [errFilePath, null];
540
- }
529
+ if (!errLine || !errCol) return [errFilePath, null];
530
+ // get errCode, errMsg
541
531
  const execArr = errCodeRegExp.exec(errMsgRaw);
542
- if (!execArr) {
543
- return [errFilePath, null];
544
- }
532
+ if (!execArr) return [errFilePath, null];
545
533
  const errCodeStr = execArr.groups?.errCode ?? "";
546
- if (!errCodeStr) {
547
- return [errFilePath, null];
548
- }
534
+ if (!errCodeStr) return [errFilePath, null];
549
535
  const line = Number(errLine);
550
536
  const col = Number(errCol);
551
537
  const errCode = Number(errCodeStr);
@@ -558,32 +544,24 @@ async function makeTscErrorInfo(errInfo) {
558
544
  }];
559
545
  }
560
546
  async function getRawErrsMapFromTsCompile(tscErrorStdout) {
561
- const rawErrsMap = new Map();
547
+ const rawErrsMap = /* @__PURE__ */ new Map();
548
+ // Merge details line with main line (i.e. which contains file path)
562
549
  const infos = await Promise.all(tscErrorStdout.split(newLineRegExp).reduce((prev, next) => {
563
- if (!next) {
564
- return prev;
565
- } else if (!next.startsWith(" ")) {
566
- prev.push(next);
567
- } else {
568
- prev[prev.length - 1] += `\n${next}`;
569
- }
550
+ if (!next) return prev;
551
+ else if (!next.startsWith(" ")) prev.push(next);
552
+ else prev[prev.length - 1] += `\n${next}`;
570
553
  return prev;
571
554
  }, []).map((errInfoLine) => makeTscErrorInfo(errInfoLine)));
572
555
  infos.forEach(([errFilePath, errInfo]) => {
573
- if (!errInfo) {
574
- return;
575
- }
576
- if (!rawErrsMap.has(errFilePath)) {
577
- rawErrsMap.set(errFilePath, [errInfo]);
578
- } else {
579
- rawErrsMap.get(errFilePath)?.push(errInfo);
580
- }
556
+ if (!errInfo) return;
557
+ if (!rawErrsMap.has(errFilePath)) rawErrsMap.set(errFilePath, [errInfo]);
558
+ else rawErrsMap.get(errFilePath)?.push(errInfo);
581
559
  });
582
560
  return rawErrsMap;
583
561
  }
584
562
 
585
563
  function createIndexMap(source) {
586
- const map = new Map();
564
+ const map = /* @__PURE__ */ new Map();
587
565
  let index = 0;
588
566
  let line = 1;
589
567
  let column = 1;
@@ -592,9 +570,7 @@ function createIndexMap(source) {
592
570
  if (char === "\n" || char === "\r\n") {
593
571
  line++;
594
572
  column = 0;
595
- } else {
596
- column++;
597
- }
573
+ } else column++;
598
574
  }
599
575
  return map;
600
576
  }
@@ -644,9 +620,7 @@ class Typechecker {
644
620
  }
645
621
  async collectTests() {
646
622
  const tests = (await Promise.all(this.getFiles().map((filepath) => this.collectFileTests(filepath)))).reduce((acc, data) => {
647
- if (!data) {
648
- return acc;
649
- }
623
+ if (!data) return acc;
650
624
  acc[data.filepath] = data;
651
625
  return acc;
652
626
  }, {});
@@ -654,17 +628,11 @@ class Typechecker {
654
628
  return tests;
655
629
  }
656
630
  markPassed(file) {
657
- if (!file.result?.state) {
658
- file.result = { state: "pass" };
659
- }
631
+ if (!file.result?.state) file.result = { state: "pass" };
660
632
  const markTasks = (tasks) => {
661
633
  for (const task of tasks) {
662
- if ("tasks" in task) {
663
- markTasks(task.tasks);
664
- }
665
- if (!task.result?.state && (task.mode === "run" || task.mode === "queued")) {
666
- task.result = { state: "pass" };
667
- }
634
+ if ("tasks" in task) markTasks(task.tasks);
635
+ if (!task.result?.state && (task.mode === "run" || task.mode === "queued")) task.result = { state: "pass" };
668
636
  }
669
637
  };
670
638
  markTasks(file.tasks);
@@ -672,9 +640,7 @@ class Typechecker {
672
640
  async prepareResults(output) {
673
641
  const typeErrors = await this.parseTscLikeOutput(output);
674
642
  const testFiles = new Set(this.getFiles());
675
- if (!this._tests) {
676
- this._tests = await this.collectTests();
677
- }
643
+ if (!this._tests) this._tests = await this.collectTests();
678
644
  const sourceErrors = [];
679
645
  const files = [];
680
646
  testFiles.forEach((path) => {
@@ -686,15 +652,13 @@ class Typechecker {
686
652
  return;
687
653
  }
688
654
  const sortedDefinitions = [...definitions.sort((a, b) => b.start - a.start)];
655
+ // has no map for ".js" files that use // @ts-check
689
656
  const traceMap = map && new TraceMap(map);
690
657
  const indexMap = createIndexMap(parsed);
691
658
  const markState = (task, state) => {
692
659
  task.result = { state: task.mode === "run" || task.mode === "only" ? state : task.mode };
693
- if (task.suite) {
694
- markState(task.suite, state);
695
- } else if (task.file && task !== task.file) {
696
- markState(task.file, state);
697
- }
660
+ if (task.suite) markState(task.suite, state);
661
+ else if (task.file && task !== task.file) markState(task.file, state);
698
662
  };
699
663
  errors.forEach(({ error, originalError }) => {
700
664
  const processedPos = traceMap ? findGeneratedPosition(traceMap, {
@@ -715,19 +679,14 @@ class Typechecker {
715
679
  };
716
680
  errors.push(error);
717
681
  if (state === "fail") {
718
- if (suite.suite) {
719
- markState(suite.suite, "fail");
720
- } else if (suite.file && suite !== suite.file) {
721
- markState(suite.file, "fail");
722
- }
682
+ if (suite.suite) markState(suite.suite, "fail");
683
+ else if (suite.file && suite !== suite.file) markState(suite.file, "fail");
723
684
  }
724
685
  });
725
686
  this.markPassed(file);
726
687
  });
727
688
  typeErrors.forEach((errors, path) => {
728
- if (!testFiles.has(path)) {
729
- sourceErrors.push(...errors.map(({ error }) => error));
730
- }
689
+ if (!testFiles.has(path)) sourceErrors.push(...errors.map(({ error }) => error));
731
690
  });
732
691
  return {
733
692
  files,
@@ -737,12 +696,13 @@ class Typechecker {
737
696
  }
738
697
  async parseTscLikeOutput(output) {
739
698
  const errorsMap = await getRawErrsMapFromTsCompile(output);
740
- const typesErrors = new Map();
699
+ const typesErrors = /* @__PURE__ */ new Map();
741
700
  errorsMap.forEach((errors, path) => {
742
701
  const filepath = resolve(this.project.config.root, path);
743
702
  const suiteErrors = errors.map((info) => {
744
703
  const limit = Error.stackTraceLimit;
745
704
  Error.stackTraceLimit = 0;
705
+ // Some expect-type errors have the most useful information on the second line e.g. `This expression is not callable.\n Type 'ExpectString<number>' has no call signatures.`
746
706
  const errMsg = info.errMsg.replace(/\r?\n\s*(Type .* has no call signatures)/g, " $1");
747
707
  const error = new TypeCheckError(errMsg, [{
748
708
  file: filepath,
@@ -755,11 +715,9 @@ class Typechecker {
755
715
  originalError: info,
756
716
  error: {
757
717
  name: error.name,
758
- nameStr: String(error.name),
759
718
  message: errMsg,
760
719
  stacks: error.stacks,
761
- stack: "",
762
- stackStr: ""
720
+ stack: ""
763
721
  }
764
722
  };
765
723
  });
@@ -769,12 +727,10 @@ class Typechecker {
769
727
  }
770
728
  async stop() {
771
729
  this.process?.kill();
772
- this.process = undefined;
730
+ this.process = void 0;
773
731
  }
774
732
  async ensurePackageInstalled(ctx, checker) {
775
- if (checker !== "tsc" && checker !== "vue-tsc") {
776
- return;
777
- }
733
+ if (checker !== "tsc" && checker !== "vue-tsc") return;
778
734
  const packageName = checker === "tsc" ? "typescript" : "vue-tsc";
779
735
  await ctx.packageInstaller.ensureInstalled(packageName, ctx.config.root);
780
736
  }
@@ -784,10 +740,7 @@ class Typechecker {
784
740
  getOutput() {
785
741
  return this._output;
786
742
  }
787
- async start() {
788
- if (this.process) {
789
- return;
790
- }
743
+ async spawn() {
791
744
  const { root, watch, typecheck } = this.project.config;
792
745
  const args = [
793
746
  "--noEmit",
@@ -797,15 +750,10 @@ class Typechecker {
797
750
  "--tsBuildInfoFile",
798
751
  join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo")
799
752
  ];
800
- if (watch) {
801
- args.push("--watch");
802
- }
803
- if (typecheck.allowJs) {
804
- args.push("--allowJs", "--checkJs");
805
- }
806
- if (typecheck.tsconfig) {
807
- args.push("-p", resolve(root, typecheck.tsconfig));
808
- }
753
+ // use builtin watcher because it's faster
754
+ if (watch) args.push("--watch");
755
+ if (typecheck.allowJs) args.push("--allowJs", "--checkJs");
756
+ if (typecheck.tsconfig) args.push("-p", resolve(root, typecheck.tsconfig));
809
757
  this._output = "";
810
758
  this._startTime = performance.now();
811
759
  const child = x(typecheck.checker, args, {
@@ -816,30 +764,62 @@ class Typechecker {
816
764
  throwOnError: false
817
765
  });
818
766
  this.process = child.process;
819
- await this._onParseStart?.();
820
767
  let rerunTriggered = false;
821
- child.process?.stdout?.on("data", (chunk) => {
822
- this._output += chunk;
823
- if (!watch) {
768
+ let dataReceived = false;
769
+ return new Promise((resolve, reject) => {
770
+ if (!child.process || !child.process.stdout) {
771
+ reject(new Error(`Failed to initialize ${typecheck.checker}. This is a bug in Vitest - please, open an issue with reproduction.`));
824
772
  return;
825
773
  }
826
- if (this._output.includes("File change detected") && !rerunTriggered) {
827
- this._onWatcherRerun?.();
828
- this._startTime = performance.now();
829
- this._result.sourceErrors = [];
830
- this._result.files = [];
831
- this._tests = null;
832
- rerunTriggered = true;
833
- }
834
- if (/Found \w+ errors*. Watching for/.test(this._output)) {
835
- rerunTriggered = false;
836
- this.prepareResults(this._output).then((result) => {
837
- this._result = result;
838
- this._onParseEnd?.(result);
839
- });
840
- this._output = "";
774
+ child.process.stdout.on("data", (chunk) => {
775
+ dataReceived = true;
776
+ this._output += chunk;
777
+ if (!watch) return;
778
+ if (this._output.includes("File change detected") && !rerunTriggered) {
779
+ this._onWatcherRerun?.();
780
+ this._startTime = performance.now();
781
+ this._result.sourceErrors = [];
782
+ this._result.files = [];
783
+ this._tests = null;
784
+ rerunTriggered = true;
785
+ }
786
+ if (/Found \w+ errors*. Watching for/.test(this._output)) {
787
+ rerunTriggered = false;
788
+ this.prepareResults(this._output).then((result) => {
789
+ this._result = result;
790
+ this._onParseEnd?.(result);
791
+ });
792
+ this._output = "";
793
+ }
794
+ });
795
+ const timeout = setTimeout(() => reject(new Error(`${typecheck.checker} spawn timed out`)), this.project.config.typecheck.spawnTimeout);
796
+ function onError(cause) {
797
+ clearTimeout(timeout);
798
+ reject(new Error("Spawning typechecker failed - is typescript installed?", { cause }));
841
799
  }
800
+ child.process.once("spawn", () => {
801
+ this._onParseStart?.();
802
+ child.process?.off("error", onError);
803
+ clearTimeout(timeout);
804
+ if (process.platform === "win32")
805
+ // on Windows, the process might be spawned but fail to start
806
+ // we wait for a potential error here. if "close" event didn't trigger,
807
+ // we resolve the promise
808
+ setTimeout(() => {
809
+ resolve({ result: child });
810
+ }, 200);
811
+ else resolve({ result: child });
812
+ });
813
+ if (process.platform === "win32") child.process.once("close", (code) => {
814
+ if (code != null && code !== 0 && !dataReceived) onError(new Error(`The ${typecheck.checker} command exited with code ${code}.`));
815
+ });
816
+ child.process.once("error", onError);
842
817
  });
818
+ }
819
+ async start() {
820
+ if (this.process) return;
821
+ const { watch } = this.project.config;
822
+ const { result: child } = await this.spawn();
843
823
  if (!watch) {
844
824
  await child;
845
825
  this._result = await this.prepareResults(this._output);
@@ -872,22 +852,19 @@ function findGeneratedPosition(traceMap, { line, column, source }) {
872
852
  column,
873
853
  source
874
854
  });
875
- if (found.line !== null) {
876
- return found;
877
- }
855
+ if (found.line !== null) return found;
856
+ // find the next source token position when the exact error position doesn't exist in source map.
857
+ // this can happen, for example, when the type error is in the comment "// @ts-expect-error"
858
+ // and comments are stripped away in the generated code.
878
859
  const mappings = [];
879
860
  eachMapping(traceMap, (m) => {
880
- if (m.source === source && m.originalLine !== null && m.originalColumn !== null && (line === m.originalLine ? column < m.originalColumn : line < m.originalLine)) {
881
- mappings.push(m);
882
- }
861
+ if (m.source === source && m.originalLine !== null && m.originalColumn !== null && (line === m.originalLine ? column < m.originalColumn : line < m.originalLine)) mappings.push(m);
883
862
  });
884
863
  const next = mappings.sort((a, b) => a.originalLine === b.originalLine ? a.originalColumn - b.originalColumn : a.originalLine - b.originalLine).at(0);
885
- if (next) {
886
- return {
887
- line: next.generatedLine,
888
- column: next.generatedColumn
889
- };
890
- }
864
+ if (next) return {
865
+ line: next.generatedLine,
866
+ column: next.generatedColumn
867
+ };
891
868
  return {
892
869
  line: null,
893
870
  column: null
@@ -1,6 +1,7 @@
1
1
  import { parseRegexp } from '@vitest/utils';
2
2
 
3
3
  const REGEXP_WRAP_PREFIX = "$$vitest:";
4
+ // Store global APIs in case process is overwritten by tests
4
5
  const processSend = process.send?.bind(process);
5
6
  const processOn = process.on?.bind(process);
6
7
  const processOff = process.off?.bind(process);
@@ -16,11 +17,9 @@ function createThreadsRpcOptions({ port }) {
16
17
  };
17
18
  }
18
19
  function disposeInternalListeners() {
19
- for (const fn of dispose) {
20
- try {
21
- fn();
22
- } catch {}
23
- }
20
+ for (const fn of dispose) try {
21
+ fn();
22
+ } catch {}
24
23
  dispose.length = 0;
25
24
  }
26
25
  function createForksRpcOptions(nodeV8) {
@@ -32,9 +31,8 @@ function createForksRpcOptions(nodeV8) {
32
31
  },
33
32
  on(fn) {
34
33
  const handler = (message, ...extras) => {
35
- if (message?.__tinypool_worker_message__) {
36
- return;
37
- }
34
+ // Do not react on Tinypool's internal messaging
35
+ if (message?.__tinypool_worker_message__) return;
38
36
  return fn(message, ...extras);
39
37
  };
40
38
  processOn("message", handler);
@@ -48,16 +46,13 @@ function createForksRpcOptions(nodeV8) {
48
46
  function unwrapSerializableConfig(config) {
49
47
  if (config.testNamePattern && typeof config.testNamePattern === "string") {
50
48
  const testNamePattern = config.testNamePattern;
51
- if (testNamePattern.startsWith(REGEXP_WRAP_PREFIX)) {
52
- config.testNamePattern = parseRegexp(testNamePattern.slice(REGEXP_WRAP_PREFIX.length));
53
- }
49
+ if (testNamePattern.startsWith(REGEXP_WRAP_PREFIX)) config.testNamePattern = parseRegexp(testNamePattern.slice(REGEXP_WRAP_PREFIX.length));
54
50
  }
55
51
  if (config.defines && Array.isArray(config.defines.keys) && config.defines.original) {
56
52
  const { keys, original } = config.defines;
57
53
  const defines = {};
58
- for (const key of keys) {
59
- defines[key] = original[key];
60
- }
54
+ // Apply all keys from the original. Entries which had undefined value are missing from original now
55
+ for (const key of keys) defines[key] = original[key];
61
56
  config.defines = defines;
62
57
  }
63
58
  return config;