tstyche 3.0.0-beta.1 → 3.0.0-beta.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/build/tstyche.js CHANGED
@@ -87,15 +87,6 @@ class DescribeResult {
87
87
  }
88
88
  }
89
89
 
90
- var ResultStatus;
91
- (function (ResultStatus) {
92
- ResultStatus["Runs"] = "runs";
93
- ResultStatus["Passed"] = "passed";
94
- ResultStatus["Failed"] = "failed";
95
- ResultStatus["Skipped"] = "skipped";
96
- ResultStatus["Todo"] = "todo";
97
- })(ResultStatus || (ResultStatus = {}));
98
-
99
90
  class ExpectResult {
100
91
  assertion;
101
92
  diagnostics = [];
@@ -144,6 +135,15 @@ class Result {
144
135
  }
145
136
  }
146
137
 
138
+ var ResultStatus;
139
+ (function (ResultStatus) {
140
+ ResultStatus["Runs"] = "runs";
141
+ ResultStatus["Passed"] = "passed";
142
+ ResultStatus["Failed"] = "failed";
143
+ ResultStatus["Skipped"] = "skipped";
144
+ ResultStatus["Todo"] = "todo";
145
+ })(ResultStatus || (ResultStatus = {}));
146
+
147
147
  class TargetResult {
148
148
  results = new Map();
149
149
  status = "runs";
@@ -193,23 +193,20 @@ class ResultHandler {
193
193
  #testResult;
194
194
  handleEvent([eventName, payload]) {
195
195
  switch (eventName) {
196
- case "run:start": {
196
+ case "run:start":
197
197
  this.#result = payload.result;
198
198
  this.#result.timing.start = Date.now();
199
199
  break;
200
- }
201
- case "run:end": {
200
+ case "run:end":
202
201
  this.#result.timing.end = Date.now();
203
202
  this.#result = undefined;
204
203
  break;
205
- }
206
- case "target:start": {
204
+ case "target:start":
207
205
  this.#result.results.push(payload.result);
208
206
  this.#targetResult = payload.result;
209
207
  this.#targetResult.timing.start = Date.now();
210
208
  break;
211
- }
212
- case "target:end": {
209
+ case "target:end":
213
210
  if (this.#targetResult.status === "failed") {
214
211
  this.#result.targetCount.failed++;
215
212
  }
@@ -220,13 +217,11 @@ class ResultHandler {
220
217
  this.#targetResult.timing.end = Date.now();
221
218
  this.#targetResult = undefined;
222
219
  break;
223
- }
224
- case "store:error": {
220
+ case "store:error":
225
221
  if (payload.diagnostics.some(({ category }) => category === "error")) {
226
222
  this.#targetResult.status = "failed";
227
223
  }
228
224
  break;
229
- }
230
225
  case "project:uses": {
231
226
  let projectResult = this.#targetResult.results.get(payload.projectConfigFilePath);
232
227
  if (!projectResult) {
@@ -236,24 +231,21 @@ class ResultHandler {
236
231
  this.#projectResult = projectResult;
237
232
  break;
238
233
  }
239
- case "project:error": {
234
+ case "project:error":
240
235
  this.#targetResult.status = "failed";
241
236
  this.#projectResult.diagnostics.push(...payload.diagnostics);
242
237
  break;
243
- }
244
- case "task:start": {
238
+ case "task:start":
245
239
  this.#projectResult.results.push(payload.result);
246
240
  this.#taskResult = payload.result;
247
241
  this.#taskResult.timing.start = Date.now();
248
242
  break;
249
- }
250
- case "task:error": {
243
+ case "task:error":
251
244
  this.#targetResult.status = "failed";
252
245
  this.#taskResult.status = "failed";
253
246
  this.#taskResult.diagnostics.push(...payload.diagnostics);
254
247
  break;
255
- }
256
- case "task:end": {
248
+ case "task:end":
257
249
  if (this.#taskResult.status === "failed" ||
258
250
  this.#taskResult.expectCount.failed > 0 ||
259
251
  this.#taskResult.testCount.failed > 0) {
@@ -268,8 +260,7 @@ class ResultHandler {
268
260
  this.#taskResult.timing.end = Date.now();
269
261
  this.#taskResult = undefined;
270
262
  break;
271
- }
272
- case "describe:start": {
263
+ case "describe:start":
273
264
  if (this.#describeResult) {
274
265
  this.#describeResult.results.push(payload.result);
275
266
  }
@@ -279,13 +270,11 @@ class ResultHandler {
279
270
  this.#describeResult = payload.result;
280
271
  this.#describeResult.timing.start = Date.now();
281
272
  break;
282
- }
283
- case "describe:end": {
273
+ case "describe:end":
284
274
  this.#describeResult.timing.end = Date.now();
285
275
  this.#describeResult = this.#describeResult.parent;
286
276
  break;
287
- }
288
- case "test:start": {
277
+ case "test:start":
289
278
  if (this.#describeResult) {
290
279
  this.#describeResult.results.push(payload.result);
291
280
  }
@@ -295,8 +284,7 @@ class ResultHandler {
295
284
  this.#testResult = payload.result;
296
285
  this.#testResult.timing.start = Date.now();
297
286
  break;
298
- }
299
- case "test:error": {
287
+ case "test:error":
300
288
  this.#result.testCount.failed++;
301
289
  this.#taskResult.testCount.failed++;
302
290
  this.#testResult.status = "failed";
@@ -304,40 +292,35 @@ class ResultHandler {
304
292
  this.#testResult.timing.end = Date.now();
305
293
  this.#testResult = undefined;
306
294
  break;
307
- }
308
- case "test:fail": {
295
+ case "test:fail":
309
296
  this.#result.testCount.failed++;
310
297
  this.#taskResult.testCount.failed++;
311
298
  this.#testResult.status = "failed";
312
299
  this.#testResult.timing.end = Date.now();
313
300
  this.#testResult = undefined;
314
301
  break;
315
- }
316
- case "test:pass": {
302
+ case "test:pass":
317
303
  this.#result.testCount.passed++;
318
304
  this.#taskResult.testCount.passed++;
319
305
  this.#testResult.status = "passed";
320
306
  this.#testResult.timing.end = Date.now();
321
307
  this.#testResult = undefined;
322
308
  break;
323
- }
324
- case "test:skip": {
309
+ case "test:skip":
325
310
  this.#result.testCount.skipped++;
326
311
  this.#taskResult.testCount.skipped++;
327
312
  this.#testResult.status = "skipped";
328
313
  this.#testResult.timing.end = Date.now();
329
314
  this.#testResult = undefined;
330
315
  break;
331
- }
332
- case "test:todo": {
316
+ case "test:todo":
333
317
  this.#result.testCount.todo++;
334
318
  this.#taskResult.testCount.todo++;
335
319
  this.#testResult.status = "todo";
336
320
  this.#testResult.timing.end = Date.now();
337
321
  this.#testResult = undefined;
338
322
  break;
339
- }
340
- case "expect:start": {
323
+ case "expect:start":
341
324
  if (this.#testResult) {
342
325
  this.#testResult.results.push(payload.result);
343
326
  }
@@ -347,8 +330,7 @@ class ResultHandler {
347
330
  this.#expectResult = payload.result;
348
331
  this.#expectResult.timing.start = Date.now();
349
332
  break;
350
- }
351
- case "expect:error": {
333
+ case "expect:error":
352
334
  this.#result.expectCount.failed++;
353
335
  this.#taskResult.expectCount.failed++;
354
336
  if (this.#testResult) {
@@ -359,8 +341,7 @@ class ResultHandler {
359
341
  this.#expectResult.timing.end = Date.now();
360
342
  this.#expectResult = undefined;
361
343
  break;
362
- }
363
- case "expect:fail": {
344
+ case "expect:fail":
364
345
  this.#result.expectCount.failed++;
365
346
  this.#taskResult.expectCount.failed++;
366
347
  if (this.#testResult) {
@@ -370,8 +351,7 @@ class ResultHandler {
370
351
  this.#expectResult.timing.end = Date.now();
371
352
  this.#expectResult = undefined;
372
353
  break;
373
- }
374
- case "expect:pass": {
354
+ case "expect:pass":
375
355
  this.#result.expectCount.passed++;
376
356
  this.#taskResult.expectCount.passed++;
377
357
  if (this.#testResult) {
@@ -381,8 +361,7 @@ class ResultHandler {
381
361
  this.#expectResult.timing.end = Date.now();
382
362
  this.#expectResult = undefined;
383
363
  break;
384
- }
385
- case "expect:skip": {
364
+ case "expect:skip":
386
365
  this.#result.expectCount.skipped++;
387
366
  this.#taskResult.expectCount.skipped++;
388
367
  if (this.#testResult) {
@@ -392,7 +371,6 @@ class ResultHandler {
392
371
  this.#expectResult.timing.end = Date.now();
393
372
  this.#expectResult = undefined;
394
373
  break;
395
- }
396
374
  }
397
375
  }
398
376
  }
@@ -503,11 +481,11 @@ class Path {
503
481
  return Path.normalizeSlashes(path.join(...filePaths));
504
482
  }
505
483
  static relative(from, to) {
506
- let relativePath = path.relative(from, to);
507
- if (!relativePath.startsWith("./")) {
508
- relativePath = `./${relativePath}`;
484
+ const relativePath = Path.normalizeSlashes(path.relative(from, to));
485
+ if (/^\.\.?\//.test(relativePath)) {
486
+ return relativePath;
509
487
  }
510
- return Path.normalizeSlashes(relativePath);
488
+ return `./${relativePath}`;
511
489
  }
512
490
  static resolve(...filePaths) {
513
491
  return Path.normalizeSlashes(path.resolve(...filePaths));
@@ -530,29 +508,25 @@ function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggl
530
508
  return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
531
509
  }
532
510
  function CodeSpanText({ diagnosticCategory, diagnosticOrigin }) {
533
- const lastLineInFile = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.sourceFile.text.length).line;
511
+ const lineMap = diagnosticOrigin.sourceFile.getLineStarts();
534
512
  const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
535
513
  const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
536
514
  const firstLine = Math.max(firstMarkedLine - 2, 0);
537
- const lastLine = Math.min(firstLine + 5, lastLineInFile);
515
+ const lastLine = Math.min(firstLine + 5, lineMap.length - 1);
538
516
  const gutterWidth = (lastLine + 1).toString().length + 2;
539
517
  let highlightColor;
540
518
  switch (diagnosticCategory) {
541
- case "error": {
519
+ case "error":
542
520
  highlightColor = "31";
543
521
  break;
544
- }
545
- case "warning": {
522
+ case "warning":
546
523
  highlightColor = "33";
547
524
  break;
548
- }
549
525
  }
550
526
  const codeSpan = [];
551
527
  for (let index = firstLine; index <= lastLine; index++) {
552
- const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
553
- const lineEnd = index === lastLineInFile
554
- ? diagnosticOrigin.sourceFile.text.length
555
- : diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
528
+ const lineStart = lineMap[index];
529
+ const lineEnd = index === lineMap.length - 1 ? diagnosticOrigin.sourceFile.text.length : lineMap[index + 1];
556
530
  const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
557
531
  if (index >= firstMarkedLine && index <= lastMarkedLine) {
558
532
  codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
@@ -588,14 +562,12 @@ function DiagnosticText({ diagnostic }) {
588
562
  function diagnosticText(diagnostic) {
589
563
  let prefix;
590
564
  switch (diagnostic.category) {
591
- case "error": {
565
+ case "error":
592
566
  prefix = jsx(Text, { color: "31", children: "Error: " });
593
567
  break;
594
- }
595
- case "warning": {
568
+ case "warning":
596
569
  prefix = jsx(Text, { color: "33", children: "Warning: " });
597
570
  break;
598
- }
599
571
  }
600
572
  return (jsx(Text, { children: [prefix, jsx(DiagnosticText, { diagnostic: diagnostic })] }));
601
573
  }
@@ -611,21 +583,18 @@ function taskStatusText(status, task) {
611
583
  let statusColor;
612
584
  let statusText;
613
585
  switch (status) {
614
- case "runs": {
586
+ case "runs":
615
587
  statusColor = "33";
616
588
  statusText = "runs";
617
589
  break;
618
- }
619
- case "passed": {
590
+ case "passed":
620
591
  statusColor = "32";
621
592
  statusText = "pass";
622
593
  break;
623
- }
624
- case "failed": {
594
+ case "failed":
625
595
  statusColor = "31";
626
596
  statusText = "fail";
627
597
  break;
628
- }
629
598
  }
630
599
  return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: task.filePath })] }));
631
600
  }
@@ -701,8 +670,8 @@ function helpText(optionDefinitions, tstycheVersion) {
701
670
  }
702
671
 
703
672
  class ConfigDiagnosticText {
704
- static doubleQuotesExpected() {
705
- return "String literal with double quotes expected.";
673
+ static expected(element) {
674
+ return `Expected ${element}.`;
706
675
  }
707
676
  static expectsListItemType(optionName, optionBrand) {
708
677
  return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
@@ -722,6 +691,9 @@ class ConfigDiagnosticText {
722
691
  return optionName;
723
692
  }
724
693
  }
694
+ static seen(element) {
695
+ return `The ${element} was seen here.`;
696
+ }
725
697
  static testFileMatchCannotStartWith(segment) {
726
698
  return [
727
699
  `A test file match pattern cannot start with '${segment}'.`,
@@ -761,9 +733,6 @@ class DiagnosticOrigin {
761
733
  const node = assertion.matcherName;
762
734
  return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
763
735
  }
764
- static fromJsonNode(node, sourceFile, skipTrivia) {
765
- return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
766
- }
767
736
  static fromNode(node, assertion) {
768
737
  return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
769
738
  }
@@ -824,8 +793,48 @@ var DiagnosticCategory;
824
793
  DiagnosticCategory["Warning"] = "warning";
825
794
  })(DiagnosticCategory || (DiagnosticCategory = {}));
826
795
 
796
+ class SourceFile {
797
+ fileName;
798
+ #lineMap;
799
+ text;
800
+ constructor(fileName, text) {
801
+ this.fileName = fileName;
802
+ this.text = text;
803
+ this.#lineMap = this.#createLineMap();
804
+ }
805
+ #createLineMap() {
806
+ const result = [0];
807
+ let position = 0;
808
+ while (position < this.text.length) {
809
+ if (this.text.charAt(position - 1) === "\r") {
810
+ position++;
811
+ }
812
+ if (this.text.charAt(position - 1) === "\n") {
813
+ result.push(position);
814
+ }
815
+ position++;
816
+ }
817
+ result.push(position);
818
+ return result;
819
+ }
820
+ getLineStarts() {
821
+ return this.#lineMap;
822
+ }
823
+ getLineAndCharacterOfPosition(position) {
824
+ const line = this.#lineMap.findLastIndex((line) => line <= position);
825
+ const character = position - this.#lineMap[line];
826
+ return { line, character };
827
+ }
828
+ }
829
+
827
830
  class OptionDefinitionsMap {
828
831
  static #definitions = [
832
+ {
833
+ brand: "string",
834
+ description: "The Url to the config file validation schema.",
835
+ group: 4,
836
+ name: "$schema",
837
+ },
829
838
  {
830
839
  brand: "string",
831
840
  description: "The path to a TSTyche configuration file.",
@@ -949,14 +958,12 @@ class OptionUsageText {
949
958
  switch (optionName) {
950
959
  case "target": {
951
960
  switch (this.#optionGroup) {
952
- case 2: {
961
+ case 2:
953
962
  usageText.push("Value for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target latest', '--target 4.9,5.3.2,current'.");
954
963
  break;
955
- }
956
- case 4: {
964
+ case 4:
957
965
  usageText.push("Item of the 'target' list must be a supported version tag.");
958
966
  break;
959
- }
960
967
  }
961
968
  const supportedTags = await this.#storeService.getSupportedTags();
962
969
  if (supportedTags != null) {
@@ -1062,13 +1069,12 @@ class OptionValidator {
1062
1069
  async check(optionName, optionValue, optionBrand, origin) {
1063
1070
  switch (optionName) {
1064
1071
  case "config":
1065
- case "rootPath": {
1072
+ case "rootPath":
1066
1073
  if (!existsSync(optionValue)) {
1067
1074
  this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
1068
1075
  }
1069
1076
  break;
1070
- }
1071
- case "target": {
1077
+ case "target":
1072
1078
  if ((await this.#storeService.validateTag(optionValue)) === false) {
1073
1079
  this.#onDiagnostics(Diagnostic.error([
1074
1080
  ConfigDiagnosticText.versionIsNotSupported(optionValue),
@@ -1076,21 +1082,18 @@ class OptionValidator {
1076
1082
  ], origin));
1077
1083
  }
1078
1084
  break;
1079
- }
1080
- case "testFileMatch": {
1085
+ case "testFileMatch":
1081
1086
  for (const segment of ["/", "../"]) {
1082
1087
  if (optionValue.startsWith(segment)) {
1083
1088
  this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
1084
1089
  }
1085
1090
  }
1086
1091
  break;
1087
- }
1088
- case "watch": {
1092
+ case "watch":
1089
1093
  if (environmentOptions.isCi) {
1090
1094
  this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
1091
1095
  }
1092
1096
  break;
1093
- }
1094
1097
  }
1095
1098
  }
1096
1099
  }
@@ -1147,20 +1150,18 @@ class CommandLineOptionsWorker {
1147
1150
  async #parseOptionValue(commandLineArgs, index, optionDefinition) {
1148
1151
  let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
1149
1152
  switch (optionDefinition.brand) {
1150
- case "bareTrue": {
1153
+ case "bareTrue":
1151
1154
  await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
1152
1155
  this.#commandLineOptions[optionDefinition.name] = true;
1153
1156
  break;
1154
- }
1155
- case "boolean": {
1157
+ case "boolean":
1156
1158
  await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
1157
1159
  this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
1158
1160
  if (optionValue === "false" || optionValue === "true") {
1159
1161
  index++;
1160
1162
  }
1161
1163
  break;
1162
- }
1163
- case "list": {
1164
+ case "list":
1164
1165
  if (optionValue !== "") {
1165
1166
  const optionValues = optionValue
1166
1167
  .split(",")
@@ -1175,8 +1176,7 @@ class CommandLineOptionsWorker {
1175
1176
  }
1176
1177
  await this.#onExpectsValue(optionDefinition);
1177
1178
  break;
1178
- }
1179
- case "string": {
1179
+ case "string":
1180
1180
  if (optionValue !== "") {
1181
1181
  if (optionDefinition.name === "config") {
1182
1182
  optionValue = Path.resolve(optionValue);
@@ -1188,7 +1188,6 @@ class CommandLineOptionsWorker {
1188
1188
  }
1189
1189
  await this.#onExpectsValue(optionDefinition);
1190
1190
  break;
1191
- }
1192
1191
  }
1193
1192
  return index;
1194
1193
  }
@@ -1197,150 +1196,277 @@ class CommandLineOptionsWorker {
1197
1196
  }
1198
1197
  }
1199
1198
 
1199
+ class JsonNode {
1200
+ origin;
1201
+ text;
1202
+ constructor(text, origin) {
1203
+ this.origin = origin;
1204
+ this.text = text;
1205
+ }
1206
+ getValue(options) {
1207
+ if (this.text == null) {
1208
+ return undefined;
1209
+ }
1210
+ if (/^['"]/.test(this.text)) {
1211
+ return this.text.slice(1, -1);
1212
+ }
1213
+ if (options?.expectsIdentifier) {
1214
+ return this.text;
1215
+ }
1216
+ if (this.text === "true") {
1217
+ return true;
1218
+ }
1219
+ if (this.text === "false") {
1220
+ return false;
1221
+ }
1222
+ if (/^\d/.test(this.text)) {
1223
+ return Number.parseFloat(this.text);
1224
+ }
1225
+ return undefined;
1226
+ }
1227
+ }
1228
+
1229
+ class JsonScanner {
1230
+ #currentPosition = 0;
1231
+ #previousPosition = 0;
1232
+ #sourceFile;
1233
+ constructor(sourceFile) {
1234
+ this.#sourceFile = sourceFile;
1235
+ }
1236
+ #getOrigin() {
1237
+ return new DiagnosticOrigin(this.#previousPosition, this.#currentPosition, this.#sourceFile);
1238
+ }
1239
+ isRead() {
1240
+ return !(this.#currentPosition < this.#sourceFile.text.length);
1241
+ }
1242
+ #peekCharacter() {
1243
+ return this.#sourceFile.text.charAt(this.#currentPosition);
1244
+ }
1245
+ #peekNextCharacter() {
1246
+ return this.#sourceFile.text.charAt(this.#currentPosition + 1);
1247
+ }
1248
+ peekToken(token) {
1249
+ this.#skipTrivia();
1250
+ return this.#peekCharacter() === token;
1251
+ }
1252
+ read() {
1253
+ this.#skipTrivia();
1254
+ this.#previousPosition = this.#currentPosition;
1255
+ if (/[\s,:\]}]/.test(this.#peekCharacter())) {
1256
+ return new JsonNode(undefined, this.#getOrigin());
1257
+ }
1258
+ let text = "";
1259
+ let closingTokenText = "";
1260
+ if (/[[{'"]/.test(this.#peekCharacter())) {
1261
+ text += this.#readCharacter();
1262
+ switch (text) {
1263
+ case "[":
1264
+ closingTokenText = "]";
1265
+ break;
1266
+ case "{":
1267
+ closingTokenText = "}";
1268
+ break;
1269
+ default:
1270
+ closingTokenText = text;
1271
+ }
1272
+ }
1273
+ while (!this.isRead()) {
1274
+ text += this.#readCharacter();
1275
+ if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1276
+ break;
1277
+ }
1278
+ }
1279
+ return new JsonNode(text, this.#getOrigin());
1280
+ }
1281
+ #readCharacter() {
1282
+ return this.#sourceFile.text.charAt(this.#currentPosition++);
1283
+ }
1284
+ readToken(token) {
1285
+ this.#skipTrivia();
1286
+ this.#previousPosition = this.#currentPosition;
1287
+ if (this.#peekCharacter() === token) {
1288
+ this.#currentPosition++;
1289
+ return new JsonNode(token, this.#getOrigin());
1290
+ }
1291
+ return new JsonNode(undefined, this.#getOrigin());
1292
+ }
1293
+ #skipTrivia() {
1294
+ while (!this.isRead()) {
1295
+ if (/\s/.test(this.#peekCharacter())) {
1296
+ this.#currentPosition++;
1297
+ continue;
1298
+ }
1299
+ if (this.#peekCharacter() === "/") {
1300
+ if (this.#peekNextCharacter() === "/") {
1301
+ this.#currentPosition += 2;
1302
+ while (!this.isRead()) {
1303
+ if (this.#readCharacter() === "\n") {
1304
+ break;
1305
+ }
1306
+ }
1307
+ continue;
1308
+ }
1309
+ if (this.#peekNextCharacter() === "*") {
1310
+ this.#currentPosition += 2;
1311
+ while (!this.isRead()) {
1312
+ if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
1313
+ this.#currentPosition += 2;
1314
+ break;
1315
+ }
1316
+ this.#currentPosition++;
1317
+ }
1318
+ continue;
1319
+ }
1320
+ }
1321
+ break;
1322
+ }
1323
+ this.#previousPosition = this.#currentPosition;
1324
+ }
1325
+ }
1326
+
1200
1327
  class ConfigFileOptionsWorker {
1201
- #compiler;
1202
1328
  #configFileOptionDefinitions;
1203
1329
  #configFileOptions;
1204
- #configFilePath;
1330
+ #jsonScanner;
1205
1331
  #onDiagnostics;
1206
1332
  #optionGroup = 4;
1207
1333
  #optionValidator;
1334
+ #sourceFile;
1208
1335
  #storeService;
1209
- constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostics) {
1210
- this.#compiler = compiler;
1336
+ constructor(configFileOptions, sourceFile, storeService, onDiagnostics) {
1211
1337
  this.#configFileOptions = configFileOptions;
1212
- this.#configFilePath = configFilePath;
1338
+ this.#sourceFile = sourceFile;
1213
1339
  this.#storeService = storeService;
1214
1340
  this.#onDiagnostics = onDiagnostics;
1215
1341
  this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
1342
+ this.#jsonScanner = new JsonScanner(this.#sourceFile);
1216
1343
  this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
1217
1344
  }
1218
- #isDoubleQuotedString(node, sourceFile) {
1219
- return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
1220
- sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
1221
- }
1222
- async parse(sourceText) {
1223
- const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
1224
- if (sourceFile.parseDiagnostics.length > 0) {
1225
- this.#onDiagnostics(Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler));
1226
- return;
1227
- }
1228
- const rootExpression = sourceFile.statements[0]?.expression;
1229
- if (!rootExpression) {
1230
- return;
1231
- }
1232
- if (!this.#compiler.isObjectLiteralExpression(rootExpression)) {
1233
- const origin = DiagnosticOrigin.fromJsonNode(rootExpression, sourceFile, this.#skipTrivia);
1234
- this.#onDiagnostics(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
1235
- return;
1236
- }
1237
- for (const property of rootExpression.properties) {
1238
- if (this.#compiler.isPropertyAssignment(property)) {
1239
- if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
1240
- const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
1241
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
1242
- continue;
1243
- }
1244
- const optionName = property.name.text;
1245
- if (optionName === "$schema") {
1246
- continue;
1247
- }
1248
- const optionDefinition = this.#configFileOptionDefinitions.get(optionName);
1249
- if (optionDefinition) {
1250
- this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
1251
- }
1252
- else {
1253
- const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
1254
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
1255
- }
1256
- }
1257
- }
1258
- return;
1345
+ #onRequiresValue(optionDefinition, jsonNode, isListItem) {
1346
+ const text = isListItem
1347
+ ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
1348
+ : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
1349
+ this.#onDiagnostics(Diagnostic.error(text, jsonNode.origin));
1259
1350
  }
1260
- async #parseOptionValue(sourceFile, valueExpression, optionDefinition, isListItem = false) {
1261
- switch (valueExpression.kind) {
1262
- case this.#compiler.SyntaxKind.TrueKeyword: {
1263
- if (optionDefinition.brand === "boolean") {
1264
- return true;
1351
+ async #parseValue(optionDefinition, isListItem = false) {
1352
+ let jsonNode;
1353
+ let optionValue;
1354
+ switch (optionDefinition.brand) {
1355
+ case "boolean": {
1356
+ jsonNode = this.#jsonScanner.read();
1357
+ optionValue = jsonNode.getValue();
1358
+ if (typeof optionValue !== "boolean") {
1359
+ this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1360
+ break;
1265
1361
  }
1266
1362
  break;
1267
1363
  }
1268
- case this.#compiler.SyntaxKind.FalseKeyword: {
1269
- if (optionDefinition.brand === "boolean") {
1270
- return false;
1364
+ case "string": {
1365
+ jsonNode = this.#jsonScanner.read();
1366
+ optionValue = jsonNode.getValue();
1367
+ if (typeof optionValue !== "string") {
1368
+ this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1369
+ break;
1271
1370
  }
1371
+ if (optionDefinition.name === "rootPath") {
1372
+ optionValue = Path.resolve(Path.dirname(this.#sourceFile.fileName), optionValue);
1373
+ }
1374
+ await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand, jsonNode.origin);
1272
1375
  break;
1273
1376
  }
1274
- case this.#compiler.SyntaxKind.StringLiteral: {
1275
- if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
1276
- const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
1277
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
1278
- return;
1377
+ case "list": {
1378
+ const leftBracketToken = this.#jsonScanner.readToken("[");
1379
+ if (!leftBracketToken.text) {
1380
+ jsonNode = this.#jsonScanner.read();
1381
+ this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1382
+ break;
1279
1383
  }
1280
- if (optionDefinition.brand === "string") {
1281
- let value = valueExpression.text;
1282
- if (optionDefinition.name === "rootPath") {
1283
- value = Path.resolve(Path.dirname(this.#configFilePath), value);
1384
+ optionValue = [];
1385
+ while (!this.#jsonScanner.isRead()) {
1386
+ if (this.#jsonScanner.peekToken("]")) {
1387
+ break;
1284
1388
  }
1285
- const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
1286
- await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
1287
- return value;
1288
- }
1289
- break;
1290
- }
1291
- case this.#compiler.SyntaxKind.ArrayLiteralExpression: {
1292
- if (optionDefinition.brand === "list") {
1293
- const value = [];
1294
- for (const element of valueExpression.elements) {
1295
- value.push(await this.#parseOptionValue(sourceFile, element, optionDefinition.items, true));
1389
+ const item = await this.#parseValue(optionDefinition.items, true);
1390
+ if (item != null) {
1391
+ optionValue.push(item);
1392
+ }
1393
+ const commaToken = this.#jsonScanner.readToken(",");
1394
+ if (!commaToken.text) {
1395
+ break;
1296
1396
  }
1297
- return value;
1397
+ }
1398
+ const rightBracketToken = this.#jsonScanner.readToken("]");
1399
+ if (!rightBracketToken.text) {
1400
+ const text = ConfigDiagnosticText.expected("closing ']'");
1401
+ const relatedText = ConfigDiagnosticText.seen("opening '['");
1402
+ const diagnostic = Diagnostic.error(text, rightBracketToken.origin).add({
1403
+ related: [Diagnostic.error(relatedText, leftBracketToken.origin)],
1404
+ });
1405
+ this.#onDiagnostics(diagnostic);
1298
1406
  }
1299
1407
  break;
1300
1408
  }
1301
1409
  }
1302
- const text = isListItem
1303
- ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
1304
- : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
1305
- const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
1306
- this.#onDiagnostics(Diagnostic.error(text, origin));
1307
- return;
1410
+ return optionValue;
1308
1411
  }
1309
- #skipTrivia(position, sourceFile) {
1310
- const { text } = sourceFile.getSourceFile();
1311
- while (position < text.length) {
1312
- if (/\s/.test(text.charAt(position))) {
1313
- position++;
1314
- continue;
1412
+ async #parseObject() {
1413
+ const leftBraceToken = this.#jsonScanner.readToken("{");
1414
+ if (this.#jsonScanner.isRead()) {
1415
+ return;
1416
+ }
1417
+ if (!leftBraceToken.text) {
1418
+ const text = ConfigDiagnosticText.expected("'{'");
1419
+ this.#onDiagnostics(Diagnostic.error(text, leftBraceToken.origin));
1420
+ return;
1421
+ }
1422
+ while (!this.#jsonScanner.isRead()) {
1423
+ if (this.#jsonScanner.peekToken("}")) {
1424
+ break;
1315
1425
  }
1316
- if (text.charAt(position) === "/") {
1317
- if (text.charAt(position + 1) === "/") {
1318
- position += 2;
1319
- while (position < text.length) {
1320
- if (text.charAt(position) === "\n") {
1321
- break;
1322
- }
1323
- position++;
1324
- }
1325
- continue;
1426
+ const optionNameNode = this.#jsonScanner.read();
1427
+ const optionName = optionNameNode.getValue({ expectsIdentifier: true });
1428
+ if (!optionName) {
1429
+ const text = ConfigDiagnosticText.expected("option name");
1430
+ this.#onDiagnostics(Diagnostic.error(text, optionNameNode.origin));
1431
+ return;
1432
+ }
1433
+ const optionDefinition = this.#configFileOptionDefinitions.get(optionName);
1434
+ if (!optionDefinition) {
1435
+ const text = ConfigDiagnosticText.unknownOption(optionName);
1436
+ this.#onDiagnostics(Diagnostic.error(text, optionNameNode.origin));
1437
+ if (this.#jsonScanner.readToken(":")) {
1438
+ this.#jsonScanner.read();
1326
1439
  }
1327
- if (text.charAt(position + 1) === "*") {
1328
- position += 2;
1329
- while (position < text.length) {
1330
- if (text.charAt(position) === "*" && text.charAt(position + 1) === "/") {
1331
- position += 2;
1332
- break;
1333
- }
1334
- position++;
1335
- }
1336
- continue;
1440
+ const commaToken = this.#jsonScanner.readToken(",");
1441
+ if (!commaToken.text) {
1442
+ break;
1337
1443
  }
1338
- position++;
1339
1444
  continue;
1340
1445
  }
1341
- break;
1446
+ if (this.#jsonScanner.peekToken(":")) {
1447
+ this.#jsonScanner.readToken(":");
1448
+ }
1449
+ const parsedValue = await this.#parseValue(optionDefinition);
1450
+ if (optionDefinition.name !== "$schema") {
1451
+ this.#configFileOptions[optionDefinition.name] = parsedValue;
1452
+ }
1453
+ const commaToken = this.#jsonScanner.readToken(",");
1454
+ if (!commaToken.text) {
1455
+ break;
1456
+ }
1342
1457
  }
1343
- return position;
1458
+ const rightBraceToken = this.#jsonScanner.readToken("}");
1459
+ if (!rightBraceToken.text) {
1460
+ const text = ConfigDiagnosticText.expected("closing '}'");
1461
+ const relatedText = ConfigDiagnosticText.seen("opening '{'");
1462
+ const diagnostic = Diagnostic.error(text, rightBraceToken.origin).add({
1463
+ related: [Diagnostic.error(relatedText, leftBraceToken.origin)],
1464
+ });
1465
+ this.#onDiagnostics(diagnostic);
1466
+ }
1467
+ }
1468
+ async parse() {
1469
+ await this.#parseObject();
1344
1470
  }
1345
1471
  }
1346
1472
 
@@ -1357,7 +1483,7 @@ class ConfigService {
1357
1483
  #configFilePath = Path.resolve(defaultOptions.rootPath, "./tstyche.config.json");
1358
1484
  #pathMatch = [];
1359
1485
  #onDiagnostics(diagnostics) {
1360
- EventEmitter.dispatch(["config:error", { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics] }]);
1486
+ EventEmitter.dispatch(["config:error", { diagnostics: [diagnostics] }]);
1361
1487
  }
1362
1488
  async parseCommandLine(commandLineArgs, storeService) {
1363
1489
  this.#commandLineOptions = {};
@@ -1379,12 +1505,9 @@ class ConfigService {
1379
1505
  const configFileText = await fs.readFile(this.#configFilePath, {
1380
1506
  encoding: "utf8",
1381
1507
  });
1382
- const compiler = await storeService.load(environmentOptions.typescriptPath != null ? "current" : "latest");
1383
- if (!compiler) {
1384
- return;
1385
- }
1386
- const configFileWorker = new ConfigFileOptionsWorker(compiler, this.#configFileOptions, this.#configFilePath, storeService, this.#onDiagnostics);
1387
- await configFileWorker.parse(configFileText);
1508
+ const sourceFile = new SourceFile(this.#configFilePath, configFileText);
1509
+ const configFileWorker = new ConfigFileOptionsWorker(this.#configFileOptions, sourceFile, storeService, this.#onDiagnostics);
1510
+ await configFileWorker.parse();
1388
1511
  }
1389
1512
  resolveConfig() {
1390
1513
  return {
@@ -1406,6 +1529,7 @@ var OptionBrand;
1406
1529
  OptionBrand["BareTrue"] = "bareTrue";
1407
1530
  OptionBrand["List"] = "list";
1408
1531
  })(OptionBrand || (OptionBrand = {}));
1532
+
1409
1533
  var OptionGroup;
1410
1534
  (function (OptionGroup) {
1411
1535
  OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
@@ -1580,6 +1704,7 @@ class RunReporter extends Reporter {
1580
1704
  #hasReportedError = false;
1581
1705
  #isFileViewExpanded = false;
1582
1706
  #resolvedConfig;
1707
+ #seenDeprecations = new Set();
1583
1708
  constructor(resolvedConfig, outputService) {
1584
1709
  super(outputService);
1585
1710
  this.#resolvedConfig = resolvedConfig;
@@ -1589,31 +1714,35 @@ class RunReporter extends Reporter {
1589
1714
  }
1590
1715
  handleEvent([eventName, payload]) {
1591
1716
  switch (eventName) {
1592
- case "run:start": {
1593
- this.#isFileViewExpanded = payload.result.tasks.length === 1 && this.#resolvedConfig.watch !== true;
1717
+ case "deprecation:info": {
1718
+ for (const diagnostic of payload.diagnostics) {
1719
+ if (!this.#seenDeprecations.has(diagnostic.text.toString())) {
1720
+ this.#fileView.addMessage(diagnosticText(diagnostic));
1721
+ this.#seenDeprecations.add(diagnostic.text.toString());
1722
+ }
1723
+ }
1594
1724
  break;
1595
1725
  }
1596
- case "store:adds": {
1726
+ case "run:start":
1727
+ this.#isFileViewExpanded = payload.result.tasks.length === 1 && this.#resolvedConfig.watch !== true;
1728
+ break;
1729
+ case "store:adds":
1597
1730
  this.outputService.writeMessage(addsPackageText(payload.packageVersion, payload.packagePath));
1598
1731
  this.#hasReportedAdds = true;
1599
1732
  break;
1600
- }
1601
- case "store:error": {
1733
+ case "store:error":
1602
1734
  for (const diagnostic of payload.diagnostics) {
1603
1735
  this.outputService.writeError(diagnosticText(diagnostic));
1604
1736
  }
1605
1737
  break;
1606
- }
1607
- case "target:start": {
1738
+ case "target:start":
1608
1739
  this.#fileCount = payload.result.tasks.length;
1609
1740
  break;
1610
- }
1611
- case "target:end": {
1741
+ case "target:end":
1612
1742
  this.#currentCompilerVersion = undefined;
1613
1743
  this.#currentProjectConfigFilePath = undefined;
1614
1744
  break;
1615
- }
1616
- case "project:uses": {
1745
+ case "project:uses":
1617
1746
  if (this.#currentCompilerVersion !== payload.compilerVersion ||
1618
1747
  this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
1619
1748
  this.outputService.writeMessage(usesCompilerText(payload.compilerVersion, payload.projectConfigFilePath, {
@@ -1624,28 +1753,24 @@ class RunReporter extends Reporter {
1624
1753
  this.#currentProjectConfigFilePath = payload.projectConfigFilePath;
1625
1754
  }
1626
1755
  break;
1627
- }
1628
- case "project:error": {
1756
+ case "project:error":
1629
1757
  for (const diagnostic of payload.diagnostics) {
1630
1758
  this.outputService.writeError(diagnosticText(diagnostic));
1631
1759
  }
1632
1760
  break;
1633
- }
1634
- case "task:start": {
1761
+ case "task:start":
1635
1762
  if (!this.#resolvedConfig.noInteractive) {
1636
1763
  this.outputService.writeMessage(taskStatusText(payload.result.status, payload.result.task));
1637
1764
  }
1638
1765
  this.#fileCount--;
1639
1766
  this.#hasReportedError = false;
1640
1767
  break;
1641
- }
1642
- case "task:error": {
1768
+ case "task:error":
1643
1769
  for (const diagnostic of payload.diagnostics) {
1644
1770
  this.#fileView.addMessage(diagnosticText(diagnostic));
1645
1771
  }
1646
1772
  break;
1647
- }
1648
- case "task:end": {
1773
+ case "task:end":
1649
1774
  if (!this.#resolvedConfig.noInteractive) {
1650
1775
  this.outputService.eraseLastLine();
1651
1776
  }
@@ -1656,33 +1781,29 @@ class RunReporter extends Reporter {
1656
1781
  this.#hasReportedError = true;
1657
1782
  }
1658
1783
  this.#fileView.clear();
1784
+ this.#seenDeprecations.clear();
1659
1785
  break;
1660
- }
1661
- case "describe:start": {
1786
+ case "describe:start":
1662
1787
  if (this.#isFileViewExpanded) {
1663
1788
  this.#fileView.beginDescribe(payload.result.describe.name);
1664
1789
  }
1665
1790
  break;
1666
- }
1667
- case "describe:end": {
1791
+ case "describe:end":
1668
1792
  if (this.#isFileViewExpanded) {
1669
1793
  this.#fileView.endDescribe();
1670
1794
  }
1671
1795
  break;
1672
- }
1673
- case "test:skip": {
1796
+ case "test:skip":
1674
1797
  if (this.#isFileViewExpanded) {
1675
1798
  this.#fileView.addTest("skip", payload.result.test.name);
1676
1799
  }
1677
1800
  break;
1678
- }
1679
- case "test:todo": {
1801
+ case "test:todo":
1680
1802
  if (this.#isFileViewExpanded) {
1681
1803
  this.#fileView.addTest("todo", payload.result.test.name);
1682
1804
  }
1683
1805
  break;
1684
- }
1685
- case "test:error": {
1806
+ case "test:error":
1686
1807
  if (this.#isFileViewExpanded) {
1687
1808
  this.#fileView.addTest("fail", payload.result.test.name);
1688
1809
  }
@@ -1690,26 +1811,22 @@ class RunReporter extends Reporter {
1690
1811
  this.#fileView.addMessage(diagnosticText(diagnostic));
1691
1812
  }
1692
1813
  break;
1693
- }
1694
- case "test:fail": {
1814
+ case "test:fail":
1695
1815
  if (this.#isFileViewExpanded) {
1696
1816
  this.#fileView.addTest("fail", payload.result.test.name);
1697
1817
  }
1698
1818
  break;
1699
- }
1700
- case "test:pass": {
1819
+ case "test:pass":
1701
1820
  if (this.#isFileViewExpanded) {
1702
1821
  this.#fileView.addTest("pass", payload.result.test.name);
1703
1822
  }
1704
1823
  break;
1705
- }
1706
1824
  case "expect:error":
1707
- case "expect:fail": {
1825
+ case "expect:fail":
1708
1826
  for (const diagnostic of payload.diagnostics) {
1709
1827
  this.#fileView.addMessage(diagnosticText(diagnostic));
1710
1828
  }
1711
1829
  break;
1712
- }
1713
1830
  }
1714
1831
  }
1715
1832
  }
@@ -1723,14 +1840,12 @@ class SetupReporter extends Reporter {
1723
1840
  if ("diagnostics" in payload) {
1724
1841
  for (const diagnostic of payload.diagnostics) {
1725
1842
  switch (diagnostic.category) {
1726
- case "error": {
1843
+ case "error":
1727
1844
  this.outputService.writeError(diagnosticText(diagnostic));
1728
1845
  break;
1729
- }
1730
- case "warning": {
1846
+ case "warning":
1731
1847
  this.outputService.writeWarning(diagnosticText(diagnostic));
1732
1848
  break;
1733
- }
1734
1849
  }
1735
1850
  }
1736
1851
  }
@@ -1739,20 +1854,17 @@ class SetupReporter extends Reporter {
1739
1854
 
1740
1855
  class SummaryReporter extends Reporter {
1741
1856
  handleEvent([eventName, payload]) {
1742
- switch (eventName) {
1743
- case "run:end": {
1744
- this.outputService.writeMessage(summaryText({
1745
- duration: payload.result.timing.duration,
1746
- expectCount: payload.result.expectCount,
1747
- fileCount: payload.result.fileCount,
1748
- onlyMatch: payload.result.resolvedConfig.only,
1749
- pathMatch: payload.result.resolvedConfig.pathMatch,
1750
- skipMatch: payload.result.resolvedConfig.skip,
1751
- targetCount: payload.result.targetCount,
1752
- testCount: payload.result.testCount,
1753
- }));
1754
- break;
1755
- }
1857
+ if (eventName === "run:end") {
1858
+ this.outputService.writeMessage(summaryText({
1859
+ duration: payload.result.timing.duration,
1860
+ expectCount: payload.result.expectCount,
1861
+ fileCount: payload.result.fileCount,
1862
+ onlyMatch: payload.result.resolvedConfig.only,
1863
+ pathMatch: payload.result.resolvedConfig.pathMatch,
1864
+ skipMatch: payload.result.resolvedConfig.skip,
1865
+ targetCount: payload.result.targetCount,
1866
+ testCount: payload.result.testCount,
1867
+ }));
1756
1868
  }
1757
1869
  }
1758
1870
  }
@@ -1760,22 +1872,19 @@ class SummaryReporter extends Reporter {
1760
1872
  class WatchReporter extends Reporter {
1761
1873
  handleEvent([eventName, payload]) {
1762
1874
  switch (eventName) {
1763
- case "run:start": {
1875
+ case "run:start":
1764
1876
  this.outputService.clearTerminal();
1765
1877
  break;
1766
- }
1767
- case "run:end": {
1878
+ case "run:end":
1768
1879
  this.outputService.writeMessage(watchUsageText());
1769
1880
  break;
1770
- }
1771
- case "watch:error": {
1881
+ case "watch:error":
1772
1882
  this.outputService.clearTerminal();
1773
1883
  for (const diagnostic of payload.diagnostics) {
1774
1884
  this.outputService.writeError(diagnosticText(diagnostic));
1775
1885
  }
1776
1886
  this.outputService.writeMessage(waitingForFileChangesText());
1777
1887
  break;
1778
- }
1779
1888
  }
1780
1889
  }
1781
1890
  }
@@ -2082,19 +2191,17 @@ class WatchService {
2082
2191
  case "\u0004":
2083
2192
  case "\u001B":
2084
2193
  case "q":
2085
- case "x": {
2194
+ case "x":
2086
2195
  onClose("watchClose");
2087
2196
  break;
2088
- }
2089
2197
  case "\u000D":
2090
2198
  case "\u0020":
2091
- case "a": {
2199
+ case "a":
2092
2200
  debounce.clearTimeout();
2093
- if (this.#watchedTestFiles.size !== 0) {
2201
+ if (this.#watchedTestFiles.size > 0) {
2094
2202
  debounce.resolveWith([...this.#watchedTestFiles.values()]);
2095
2203
  }
2096
2204
  break;
2097
- }
2098
2205
  }
2099
2206
  };
2100
2207
  this.#inputService = new InputService(onInput);
@@ -2176,23 +2283,21 @@ class TestMember {
2176
2283
  return node.parent;
2177
2284
  };
2178
2285
  switch (this.brand) {
2179
- case "describe": {
2286
+ case "describe":
2180
2287
  for (const member of this.members) {
2181
2288
  if (member.brand === "expect") {
2182
2289
  diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
2183
2290
  }
2184
2291
  }
2185
2292
  break;
2186
- }
2187
2293
  case "test":
2188
- case "expect": {
2294
+ case "expect":
2189
2295
  for (const member of this.members) {
2190
2296
  if (member.brand !== "expect") {
2191
2297
  diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
2192
2298
  }
2193
2299
  }
2194
2300
  break;
2195
- }
2196
2301
  }
2197
2302
  return diagnostics;
2198
2303
  }
@@ -2277,22 +2382,18 @@ class IdentifierLookup {
2277
2382
  break;
2278
2383
  }
2279
2384
  switch (expression.name.getText()) {
2280
- case "fail": {
2385
+ case "fail":
2281
2386
  flags |= 1;
2282
2387
  break;
2283
- }
2284
- case "only": {
2388
+ case "only":
2285
2389
  flags |= 2;
2286
2390
  break;
2287
- }
2288
- case "skip": {
2391
+ case "skip":
2289
2392
  flags |= 4;
2290
2393
  break;
2291
- }
2292
- case "todo": {
2394
+ case "todo":
2293
2395
  flags |= 8;
2294
2396
  break;
2295
- }
2296
2397
  }
2297
2398
  expression = expression.expression;
2298
2399
  }
@@ -2403,6 +2504,7 @@ var TestMemberBrand;
2403
2504
  TestMemberBrand["Test"] = "test";
2404
2505
  TestMemberBrand["Expect"] = "expect";
2405
2506
  })(TestMemberBrand || (TestMemberBrand = {}));
2507
+
2406
2508
  var TestMemberFlags;
2407
2509
  (function (TestMemberFlags) {
2408
2510
  TestMemberFlags[TestMemberFlags["None"] = 0] = "None";
@@ -2546,6 +2648,12 @@ class ExpectDiagnosticText {
2546
2648
  static componentDoesNotAcceptProps(isTypeNode) {
2547
2649
  return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
2548
2650
  }
2651
+ static matcherIsDeprecated(matcherNameText) {
2652
+ return [
2653
+ `The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 4.`,
2654
+ "To learn more, visit https://tstyche.org/releases/tstyche-3",
2655
+ ];
2656
+ }
2549
2657
  static matcherIsNotSupported(matcherNameText) {
2550
2658
  return `The '.${matcherNameText}()' matcher is not supported.`;
2551
2659
  }
@@ -3160,6 +3268,11 @@ class ExpectService {
3160
3268
  }
3161
3269
  match(assertion, onDiagnostics) {
3162
3270
  const matcherNameText = assertion.matcherName.getText();
3271
+ if (matcherNameText === "toMatch") {
3272
+ const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
3273
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
3274
+ EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
3275
+ }
3163
3276
  if (!assertion.source[0]) {
3164
3277
  this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
3165
3278
  return;
@@ -3170,13 +3283,12 @@ class ExpectService {
3170
3283
  case "toBe":
3171
3284
  case "toBeAssignableTo":
3172
3285
  case "toBeAssignableWith":
3173
- case "toMatch": {
3286
+ case "toMatch":
3174
3287
  if (!assertion.target[0]) {
3175
3288
  this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
3176
3289
  return;
3177
3290
  }
3178
3291
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
3179
- }
3180
3292
  case "toBeAny":
3181
3293
  case "toBeBigInt":
3182
3294
  case "toBeBoolean":
@@ -3188,27 +3300,26 @@ class ExpectService {
3188
3300
  case "toBeUndefined":
3189
3301
  case "toBeUniqueSymbol":
3190
3302
  case "toBeUnknown":
3191
- case "toBeVoid": {
3303
+ case "toBeVoid":
3192
3304
  return this[matcherNameText].match(matchWorker, assertion.source[0]);
3193
- }
3194
- case "toHaveProperty": {
3305
+ case "toHaveProperty":
3195
3306
  if (!assertion.target[0]) {
3196
3307
  this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
3197
3308
  return;
3198
3309
  }
3199
3310
  return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
3200
- }
3201
- case "toRaiseError": {
3311
+ case "toRaiseError":
3202
3312
  return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
3203
- }
3204
- default: {
3205
- const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
3206
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
3207
- onDiagnostics(Diagnostic.error(text, origin));
3208
- }
3313
+ default:
3314
+ this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
3209
3315
  }
3210
3316
  return;
3211
3317
  }
3318
+ #onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics) {
3319
+ const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
3320
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
3321
+ onDiagnostics(Diagnostic.error(text, origin));
3322
+ }
3212
3323
  #onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
3213
3324
  const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
3214
3325
  const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
@@ -3275,18 +3386,15 @@ class TestTreeWorker {
3275
3386
  break;
3276
3387
  }
3277
3388
  switch (member.brand) {
3278
- case "describe": {
3389
+ case "describe":
3279
3390
  this.#visitDescribe(member, runMode, parentResult);
3280
3391
  break;
3281
- }
3282
- case "test": {
3392
+ case "test":
3283
3393
  this.#visitTest(member, runMode, parentResult);
3284
3394
  break;
3285
- }
3286
- case "expect": {
3395
+ case "expect":
3287
3396
  this.#visitAssertion(member, runMode, parentResult);
3288
3397
  break;
3289
- }
3290
3398
  }
3291
3399
  }
3292
3400
  }
@@ -3510,7 +3618,7 @@ class TSTyche {
3510
3618
  #runner;
3511
3619
  #selectService;
3512
3620
  #storeService;
3513
- static version = "3.0.0-beta.1";
3621
+ static version = "3.0.0-beta.3";
3514
3622
  constructor(resolvedConfig, outputService, selectService, storeService) {
3515
3623
  this.#resolvedConfig = resolvedConfig;
3516
3624
  this.#outputService = outputService;
@@ -4052,7 +4160,7 @@ class Cli {
4052
4160
  }
4053
4161
  const selectService = new SelectService(resolvedConfig);
4054
4162
  let testFiles = [];
4055
- if (resolvedConfig.testFileMatch.length !== 0) {
4163
+ if (resolvedConfig.testFileMatch.length > 0) {
4056
4164
  testFiles = await selectService.selectFiles();
4057
4165
  if (testFiles.length === 0) {
4058
4166
  if (commandLineArguments.includes("--watch")) {
@@ -4103,4 +4211,4 @@ class Cli {
4103
4211
  }
4104
4212
  }
4105
4213
 
4106
- export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RunReporter, Runner, Scribbler, SelectDiagnosticText, SelectService, SetupReporter, StoreService, SummaryReporter, TSTyche, TargetResult, Task, TaskResult, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
4214
+ export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RunReporter, Runner, Scribbler, SelectDiagnosticText, SelectService, SetupReporter, SourceFile, StoreService, SummaryReporter, TSTyche, TargetResult, Task, TaskResult, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };