tstyche 3.0.0-beta.1 → 3.0.0-beta.2

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
  }
@@ -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,296 @@ 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
+ if (/[:\]}]/.test(this.#peekCharacter())) {
1255
+ return new JsonNode(undefined, this.#getOrigin());
1256
+ }
1257
+ let quoteCharacter = "";
1258
+ let text = "";
1259
+ this.#previousPosition = this.#currentPosition;
1260
+ if (/['"]/.test(this.#peekCharacter())) {
1261
+ quoteCharacter = text += this.#readCharacter();
1262
+ }
1263
+ while (!this.isRead()) {
1264
+ text += this.#readCharacter();
1265
+ if (text.slice(-1) === quoteCharacter || (!quoteCharacter && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1266
+ break;
1267
+ }
1268
+ }
1269
+ return new JsonNode(text, this.#getOrigin());
1270
+ }
1271
+ #readCharacter() {
1272
+ return this.#sourceFile.text.charAt(this.#currentPosition++);
1273
+ }
1274
+ readToken(token) {
1275
+ this.#skipTrivia();
1276
+ this.#previousPosition = this.#currentPosition;
1277
+ if (this.#peekCharacter() === token) {
1278
+ this.#currentPosition++;
1279
+ return new JsonNode(token, this.#getOrigin());
1280
+ }
1281
+ return new JsonNode(undefined, this.#getOrigin());
1282
+ }
1283
+ skip() {
1284
+ this.#skipTrivia();
1285
+ this.#previousPosition = this.#currentPosition;
1286
+ if (/[\s,:\]}]/.test(this.#peekCharacter())) {
1287
+ return new JsonNode(undefined, this.#getOrigin());
1288
+ }
1289
+ let text = "";
1290
+ let closingCharacter = "";
1291
+ if (/[[{'"]/.test(this.#peekCharacter())) {
1292
+ text += this.#readCharacter();
1293
+ switch (text) {
1294
+ case "[":
1295
+ closingCharacter = "]";
1296
+ break;
1297
+ case "{":
1298
+ closingCharacter = "}";
1299
+ break;
1300
+ default:
1301
+ closingCharacter = text;
1302
+ }
1303
+ }
1304
+ while (!this.isRead()) {
1305
+ text += this.#readCharacter();
1306
+ if (text.slice(-1) === closingCharacter || (!closingCharacter && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1307
+ break;
1308
+ }
1309
+ }
1310
+ return new JsonNode(text, this.#getOrigin());
1311
+ }
1312
+ #skipTrivia() {
1313
+ while (!this.isRead()) {
1314
+ if (/\s/.test(this.#peekCharacter())) {
1315
+ this.#currentPosition++;
1316
+ continue;
1317
+ }
1318
+ if (this.#peekCharacter() === "/") {
1319
+ if (this.#peekNextCharacter() === "/") {
1320
+ this.#currentPosition += 2;
1321
+ while (!this.isRead()) {
1322
+ if (this.#readCharacter() === "\n") {
1323
+ break;
1324
+ }
1325
+ }
1326
+ continue;
1327
+ }
1328
+ if (this.#peekNextCharacter() === "*") {
1329
+ this.#currentPosition += 2;
1330
+ while (!this.isRead()) {
1331
+ if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
1332
+ this.#currentPosition += 2;
1333
+ break;
1334
+ }
1335
+ this.#currentPosition++;
1336
+ }
1337
+ continue;
1338
+ }
1339
+ }
1340
+ break;
1341
+ }
1342
+ this.#previousPosition = this.#currentPosition;
1343
+ }
1344
+ }
1345
+
1200
1346
  class ConfigFileOptionsWorker {
1201
- #compiler;
1202
1347
  #configFileOptionDefinitions;
1203
1348
  #configFileOptions;
1204
- #configFilePath;
1349
+ #jsonScanner;
1205
1350
  #onDiagnostics;
1206
1351
  #optionGroup = 4;
1207
1352
  #optionValidator;
1353
+ #sourceFile;
1208
1354
  #storeService;
1209
- constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostics) {
1210
- this.#compiler = compiler;
1355
+ constructor(configFileOptions, sourceFile, storeService, onDiagnostics) {
1211
1356
  this.#configFileOptions = configFileOptions;
1212
- this.#configFilePath = configFilePath;
1357
+ this.#sourceFile = sourceFile;
1213
1358
  this.#storeService = storeService;
1214
1359
  this.#onDiagnostics = onDiagnostics;
1215
1360
  this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
1361
+ this.#jsonScanner = new JsonScanner(this.#sourceFile);
1216
1362
  this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
1217
1363
  }
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;
1364
+ #onRequiresValue(optionDefinition, jsonNode, isListItem) {
1365
+ const text = isListItem
1366
+ ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
1367
+ : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
1368
+ this.#onDiagnostics(Diagnostic.error(text, jsonNode.origin));
1259
1369
  }
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;
1370
+ async #parseValue(optionDefinition, isListItem = false) {
1371
+ let jsonNode;
1372
+ let optionValue;
1373
+ switch (optionDefinition.brand) {
1374
+ case "boolean": {
1375
+ jsonNode = this.#jsonScanner.read();
1376
+ optionValue = jsonNode.getValue();
1377
+ if (typeof optionValue !== "boolean") {
1378
+ this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1379
+ break;
1265
1380
  }
1266
1381
  break;
1267
1382
  }
1268
- case this.#compiler.SyntaxKind.FalseKeyword: {
1269
- if (optionDefinition.brand === "boolean") {
1270
- return false;
1383
+ case "string": {
1384
+ jsonNode = this.#jsonScanner.read();
1385
+ optionValue = jsonNode.getValue();
1386
+ if (typeof optionValue !== "string") {
1387
+ this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1388
+ break;
1271
1389
  }
1390
+ if (optionDefinition.name === "rootPath") {
1391
+ optionValue = Path.resolve(Path.dirname(this.#sourceFile.fileName), optionValue);
1392
+ }
1393
+ await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand, jsonNode.origin);
1272
1394
  break;
1273
1395
  }
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;
1396
+ case "list": {
1397
+ const leftBracketToken = this.#jsonScanner.readToken("[");
1398
+ if (!leftBracketToken.text) {
1399
+ jsonNode = this.#jsonScanner.skip();
1400
+ this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1401
+ break;
1279
1402
  }
1280
- if (optionDefinition.brand === "string") {
1281
- let value = valueExpression.text;
1282
- if (optionDefinition.name === "rootPath") {
1283
- value = Path.resolve(Path.dirname(this.#configFilePath), value);
1403
+ optionValue = [];
1404
+ while (!this.#jsonScanner.isRead()) {
1405
+ if (this.#jsonScanner.peekToken("]")) {
1406
+ break;
1284
1407
  }
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));
1408
+ const item = await this.#parseValue(optionDefinition.items, true);
1409
+ if (item != null) {
1410
+ optionValue.push(item);
1411
+ }
1412
+ const commaToken = this.#jsonScanner.readToken(",");
1413
+ if (!commaToken.text) {
1414
+ break;
1296
1415
  }
1297
- return value;
1416
+ }
1417
+ const rightBracketToken = this.#jsonScanner.readToken("]");
1418
+ if (!rightBracketToken.text) {
1419
+ const text = ConfigDiagnosticText.expected("closing ']'");
1420
+ const relatedText = ConfigDiagnosticText.seen("opening '['");
1421
+ const diagnostic = Diagnostic.error(text, rightBracketToken.origin).add({
1422
+ related: [Diagnostic.error(relatedText, leftBracketToken.origin)],
1423
+ });
1424
+ this.#onDiagnostics(diagnostic);
1298
1425
  }
1299
1426
  break;
1300
1427
  }
1301
1428
  }
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;
1429
+ return optionValue;
1308
1430
  }
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;
1431
+ async #parseObject() {
1432
+ const leftBraceToken = this.#jsonScanner.readToken("{");
1433
+ if (this.#jsonScanner.isRead()) {
1434
+ return;
1435
+ }
1436
+ if (!leftBraceToken.text) {
1437
+ const text = ConfigDiagnosticText.expected("'{'");
1438
+ this.#onDiagnostics(Diagnostic.error(text, leftBraceToken.origin));
1439
+ return;
1440
+ }
1441
+ while (!this.#jsonScanner.isRead()) {
1442
+ if (this.#jsonScanner.peekToken("}")) {
1443
+ break;
1315
1444
  }
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;
1445
+ const optionNameNode = this.#jsonScanner.read();
1446
+ const optionName = optionNameNode.getValue({ expectsIdentifier: true });
1447
+ if (!optionName) {
1448
+ const text = ConfigDiagnosticText.expected("option name");
1449
+ this.#onDiagnostics(Diagnostic.error(text, optionNameNode.origin));
1450
+ return;
1451
+ }
1452
+ const optionDefinition = this.#configFileOptionDefinitions.get(optionName);
1453
+ if (!optionDefinition) {
1454
+ const text = ConfigDiagnosticText.unknownOption(optionName);
1455
+ this.#onDiagnostics(Diagnostic.error(text, optionNameNode.origin));
1456
+ if (this.#jsonScanner.readToken(":")) {
1457
+ this.#jsonScanner.skip();
1326
1458
  }
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;
1459
+ const commaToken = this.#jsonScanner.readToken(",");
1460
+ if (!commaToken.text) {
1461
+ break;
1337
1462
  }
1338
- position++;
1339
1463
  continue;
1340
1464
  }
1341
- break;
1465
+ if (this.#jsonScanner.peekToken(":")) {
1466
+ this.#jsonScanner.readToken(":");
1467
+ }
1468
+ const parsedValue = await this.#parseValue(optionDefinition);
1469
+ if (optionDefinition.name !== "$schema") {
1470
+ this.#configFileOptions[optionDefinition.name] = parsedValue;
1471
+ }
1472
+ const commaToken = this.#jsonScanner.readToken(",");
1473
+ if (!commaToken.text) {
1474
+ break;
1475
+ }
1476
+ }
1477
+ const rightBraceToken = this.#jsonScanner.readToken("}");
1478
+ if (!rightBraceToken.text) {
1479
+ const text = ConfigDiagnosticText.expected("closing '}'");
1480
+ const relatedText = ConfigDiagnosticText.seen("opening '{'");
1481
+ const diagnostic = Diagnostic.error(text, rightBraceToken.origin).add({
1482
+ related: [Diagnostic.error(relatedText, leftBraceToken.origin)],
1483
+ });
1484
+ this.#onDiagnostics(diagnostic);
1342
1485
  }
1343
- return position;
1486
+ }
1487
+ async parse() {
1488
+ await this.#parseObject();
1344
1489
  }
1345
1490
  }
1346
1491
 
@@ -1357,7 +1502,7 @@ class ConfigService {
1357
1502
  #configFilePath = Path.resolve(defaultOptions.rootPath, "./tstyche.config.json");
1358
1503
  #pathMatch = [];
1359
1504
  #onDiagnostics(diagnostics) {
1360
- EventEmitter.dispatch(["config:error", { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics] }]);
1505
+ EventEmitter.dispatch(["config:error", { diagnostics: [diagnostics] }]);
1361
1506
  }
1362
1507
  async parseCommandLine(commandLineArgs, storeService) {
1363
1508
  this.#commandLineOptions = {};
@@ -1379,12 +1524,9 @@ class ConfigService {
1379
1524
  const configFileText = await fs.readFile(this.#configFilePath, {
1380
1525
  encoding: "utf8",
1381
1526
  });
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);
1527
+ const sourceFile = new SourceFile(this.#configFilePath, configFileText);
1528
+ const configFileWorker = new ConfigFileOptionsWorker(this.#configFileOptions, sourceFile, storeService, this.#onDiagnostics);
1529
+ await configFileWorker.parse();
1388
1530
  }
1389
1531
  resolveConfig() {
1390
1532
  return {
@@ -1406,6 +1548,7 @@ var OptionBrand;
1406
1548
  OptionBrand["BareTrue"] = "bareTrue";
1407
1549
  OptionBrand["List"] = "list";
1408
1550
  })(OptionBrand || (OptionBrand = {}));
1551
+
1409
1552
  var OptionGroup;
1410
1553
  (function (OptionGroup) {
1411
1554
  OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
@@ -1589,31 +1732,26 @@ class RunReporter extends Reporter {
1589
1732
  }
1590
1733
  handleEvent([eventName, payload]) {
1591
1734
  switch (eventName) {
1592
- case "run:start": {
1735
+ case "run:start":
1593
1736
  this.#isFileViewExpanded = payload.result.tasks.length === 1 && this.#resolvedConfig.watch !== true;
1594
1737
  break;
1595
- }
1596
- case "store:adds": {
1738
+ case "store:adds":
1597
1739
  this.outputService.writeMessage(addsPackageText(payload.packageVersion, payload.packagePath));
1598
1740
  this.#hasReportedAdds = true;
1599
1741
  break;
1600
- }
1601
- case "store:error": {
1742
+ case "store:error":
1602
1743
  for (const diagnostic of payload.diagnostics) {
1603
1744
  this.outputService.writeError(diagnosticText(diagnostic));
1604
1745
  }
1605
1746
  break;
1606
- }
1607
- case "target:start": {
1747
+ case "target:start":
1608
1748
  this.#fileCount = payload.result.tasks.length;
1609
1749
  break;
1610
- }
1611
- case "target:end": {
1750
+ case "target:end":
1612
1751
  this.#currentCompilerVersion = undefined;
1613
1752
  this.#currentProjectConfigFilePath = undefined;
1614
1753
  break;
1615
- }
1616
- case "project:uses": {
1754
+ case "project:uses":
1617
1755
  if (this.#currentCompilerVersion !== payload.compilerVersion ||
1618
1756
  this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
1619
1757
  this.outputService.writeMessage(usesCompilerText(payload.compilerVersion, payload.projectConfigFilePath, {
@@ -1624,28 +1762,24 @@ class RunReporter extends Reporter {
1624
1762
  this.#currentProjectConfigFilePath = payload.projectConfigFilePath;
1625
1763
  }
1626
1764
  break;
1627
- }
1628
- case "project:error": {
1765
+ case "project:error":
1629
1766
  for (const diagnostic of payload.diagnostics) {
1630
1767
  this.outputService.writeError(diagnosticText(diagnostic));
1631
1768
  }
1632
1769
  break;
1633
- }
1634
- case "task:start": {
1770
+ case "task:start":
1635
1771
  if (!this.#resolvedConfig.noInteractive) {
1636
1772
  this.outputService.writeMessage(taskStatusText(payload.result.status, payload.result.task));
1637
1773
  }
1638
1774
  this.#fileCount--;
1639
1775
  this.#hasReportedError = false;
1640
1776
  break;
1641
- }
1642
- case "task:error": {
1777
+ case "task:error":
1643
1778
  for (const diagnostic of payload.diagnostics) {
1644
1779
  this.#fileView.addMessage(diagnosticText(diagnostic));
1645
1780
  }
1646
1781
  break;
1647
- }
1648
- case "task:end": {
1782
+ case "task:end":
1649
1783
  if (!this.#resolvedConfig.noInteractive) {
1650
1784
  this.outputService.eraseLastLine();
1651
1785
  }
@@ -1657,32 +1791,27 @@ class RunReporter extends Reporter {
1657
1791
  }
1658
1792
  this.#fileView.clear();
1659
1793
  break;
1660
- }
1661
- case "describe:start": {
1794
+ case "describe:start":
1662
1795
  if (this.#isFileViewExpanded) {
1663
1796
  this.#fileView.beginDescribe(payload.result.describe.name);
1664
1797
  }
1665
1798
  break;
1666
- }
1667
- case "describe:end": {
1799
+ case "describe:end":
1668
1800
  if (this.#isFileViewExpanded) {
1669
1801
  this.#fileView.endDescribe();
1670
1802
  }
1671
1803
  break;
1672
- }
1673
- case "test:skip": {
1804
+ case "test:skip":
1674
1805
  if (this.#isFileViewExpanded) {
1675
1806
  this.#fileView.addTest("skip", payload.result.test.name);
1676
1807
  }
1677
1808
  break;
1678
- }
1679
- case "test:todo": {
1809
+ case "test:todo":
1680
1810
  if (this.#isFileViewExpanded) {
1681
1811
  this.#fileView.addTest("todo", payload.result.test.name);
1682
1812
  }
1683
1813
  break;
1684
- }
1685
- case "test:error": {
1814
+ case "test:error":
1686
1815
  if (this.#isFileViewExpanded) {
1687
1816
  this.#fileView.addTest("fail", payload.result.test.name);
1688
1817
  }
@@ -1690,26 +1819,22 @@ class RunReporter extends Reporter {
1690
1819
  this.#fileView.addMessage(diagnosticText(diagnostic));
1691
1820
  }
1692
1821
  break;
1693
- }
1694
- case "test:fail": {
1822
+ case "test:fail":
1695
1823
  if (this.#isFileViewExpanded) {
1696
1824
  this.#fileView.addTest("fail", payload.result.test.name);
1697
1825
  }
1698
1826
  break;
1699
- }
1700
- case "test:pass": {
1827
+ case "test:pass":
1701
1828
  if (this.#isFileViewExpanded) {
1702
1829
  this.#fileView.addTest("pass", payload.result.test.name);
1703
1830
  }
1704
1831
  break;
1705
- }
1706
1832
  case "expect:error":
1707
- case "expect:fail": {
1833
+ case "expect:fail":
1708
1834
  for (const diagnostic of payload.diagnostics) {
1709
1835
  this.#fileView.addMessage(diagnosticText(diagnostic));
1710
1836
  }
1711
1837
  break;
1712
- }
1713
1838
  }
1714
1839
  }
1715
1840
  }
@@ -1723,14 +1848,12 @@ class SetupReporter extends Reporter {
1723
1848
  if ("diagnostics" in payload) {
1724
1849
  for (const diagnostic of payload.diagnostics) {
1725
1850
  switch (diagnostic.category) {
1726
- case "error": {
1851
+ case "error":
1727
1852
  this.outputService.writeError(diagnosticText(diagnostic));
1728
1853
  break;
1729
- }
1730
- case "warning": {
1854
+ case "warning":
1731
1855
  this.outputService.writeWarning(diagnosticText(diagnostic));
1732
1856
  break;
1733
- }
1734
1857
  }
1735
1858
  }
1736
1859
  }
@@ -1739,20 +1862,17 @@ class SetupReporter extends Reporter {
1739
1862
 
1740
1863
  class SummaryReporter extends Reporter {
1741
1864
  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
- }
1865
+ if (eventName === "run:end") {
1866
+ this.outputService.writeMessage(summaryText({
1867
+ duration: payload.result.timing.duration,
1868
+ expectCount: payload.result.expectCount,
1869
+ fileCount: payload.result.fileCount,
1870
+ onlyMatch: payload.result.resolvedConfig.only,
1871
+ pathMatch: payload.result.resolvedConfig.pathMatch,
1872
+ skipMatch: payload.result.resolvedConfig.skip,
1873
+ targetCount: payload.result.targetCount,
1874
+ testCount: payload.result.testCount,
1875
+ }));
1756
1876
  }
1757
1877
  }
1758
1878
  }
@@ -1760,22 +1880,19 @@ class SummaryReporter extends Reporter {
1760
1880
  class WatchReporter extends Reporter {
1761
1881
  handleEvent([eventName, payload]) {
1762
1882
  switch (eventName) {
1763
- case "run:start": {
1883
+ case "run:start":
1764
1884
  this.outputService.clearTerminal();
1765
1885
  break;
1766
- }
1767
- case "run:end": {
1886
+ case "run:end":
1768
1887
  this.outputService.writeMessage(watchUsageText());
1769
1888
  break;
1770
- }
1771
- case "watch:error": {
1889
+ case "watch:error":
1772
1890
  this.outputService.clearTerminal();
1773
1891
  for (const diagnostic of payload.diagnostics) {
1774
1892
  this.outputService.writeError(diagnosticText(diagnostic));
1775
1893
  }
1776
1894
  this.outputService.writeMessage(waitingForFileChangesText());
1777
1895
  break;
1778
- }
1779
1896
  }
1780
1897
  }
1781
1898
  }
@@ -2082,19 +2199,17 @@ class WatchService {
2082
2199
  case "\u0004":
2083
2200
  case "\u001B":
2084
2201
  case "q":
2085
- case "x": {
2202
+ case "x":
2086
2203
  onClose("watchClose");
2087
2204
  break;
2088
- }
2089
2205
  case "\u000D":
2090
2206
  case "\u0020":
2091
- case "a": {
2207
+ case "a":
2092
2208
  debounce.clearTimeout();
2093
- if (this.#watchedTestFiles.size !== 0) {
2209
+ if (this.#watchedTestFiles.size > 0) {
2094
2210
  debounce.resolveWith([...this.#watchedTestFiles.values()]);
2095
2211
  }
2096
2212
  break;
2097
- }
2098
2213
  }
2099
2214
  };
2100
2215
  this.#inputService = new InputService(onInput);
@@ -2176,23 +2291,21 @@ class TestMember {
2176
2291
  return node.parent;
2177
2292
  };
2178
2293
  switch (this.brand) {
2179
- case "describe": {
2294
+ case "describe":
2180
2295
  for (const member of this.members) {
2181
2296
  if (member.brand === "expect") {
2182
2297
  diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
2183
2298
  }
2184
2299
  }
2185
2300
  break;
2186
- }
2187
2301
  case "test":
2188
- case "expect": {
2302
+ case "expect":
2189
2303
  for (const member of this.members) {
2190
2304
  if (member.brand !== "expect") {
2191
2305
  diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
2192
2306
  }
2193
2307
  }
2194
2308
  break;
2195
- }
2196
2309
  }
2197
2310
  return diagnostics;
2198
2311
  }
@@ -2277,22 +2390,18 @@ class IdentifierLookup {
2277
2390
  break;
2278
2391
  }
2279
2392
  switch (expression.name.getText()) {
2280
- case "fail": {
2393
+ case "fail":
2281
2394
  flags |= 1;
2282
2395
  break;
2283
- }
2284
- case "only": {
2396
+ case "only":
2285
2397
  flags |= 2;
2286
2398
  break;
2287
- }
2288
- case "skip": {
2399
+ case "skip":
2289
2400
  flags |= 4;
2290
2401
  break;
2291
- }
2292
- case "todo": {
2402
+ case "todo":
2293
2403
  flags |= 8;
2294
2404
  break;
2295
- }
2296
2405
  }
2297
2406
  expression = expression.expression;
2298
2407
  }
@@ -2403,6 +2512,7 @@ var TestMemberBrand;
2403
2512
  TestMemberBrand["Test"] = "test";
2404
2513
  TestMemberBrand["Expect"] = "expect";
2405
2514
  })(TestMemberBrand || (TestMemberBrand = {}));
2515
+
2406
2516
  var TestMemberFlags;
2407
2517
  (function (TestMemberFlags) {
2408
2518
  TestMemberFlags[TestMemberFlags["None"] = 0] = "None";
@@ -3170,13 +3280,12 @@ class ExpectService {
3170
3280
  case "toBe":
3171
3281
  case "toBeAssignableTo":
3172
3282
  case "toBeAssignableWith":
3173
- case "toMatch": {
3283
+ case "toMatch":
3174
3284
  if (!assertion.target[0]) {
3175
3285
  this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
3176
3286
  return;
3177
3287
  }
3178
3288
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
3179
- }
3180
3289
  case "toBeAny":
3181
3290
  case "toBeBigInt":
3182
3291
  case "toBeBoolean":
@@ -3188,27 +3297,26 @@ class ExpectService {
3188
3297
  case "toBeUndefined":
3189
3298
  case "toBeUniqueSymbol":
3190
3299
  case "toBeUnknown":
3191
- case "toBeVoid": {
3300
+ case "toBeVoid":
3192
3301
  return this[matcherNameText].match(matchWorker, assertion.source[0]);
3193
- }
3194
- case "toHaveProperty": {
3302
+ case "toHaveProperty":
3195
3303
  if (!assertion.target[0]) {
3196
3304
  this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
3197
3305
  return;
3198
3306
  }
3199
3307
  return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
3200
- }
3201
- case "toRaiseError": {
3308
+ case "toRaiseError":
3202
3309
  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
- }
3310
+ default:
3311
+ this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
3209
3312
  }
3210
3313
  return;
3211
3314
  }
3315
+ #onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics) {
3316
+ const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
3317
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
3318
+ onDiagnostics(Diagnostic.error(text, origin));
3319
+ }
3212
3320
  #onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
3213
3321
  const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
3214
3322
  const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
@@ -3275,18 +3383,15 @@ class TestTreeWorker {
3275
3383
  break;
3276
3384
  }
3277
3385
  switch (member.brand) {
3278
- case "describe": {
3386
+ case "describe":
3279
3387
  this.#visitDescribe(member, runMode, parentResult);
3280
3388
  break;
3281
- }
3282
- case "test": {
3389
+ case "test":
3283
3390
  this.#visitTest(member, runMode, parentResult);
3284
3391
  break;
3285
- }
3286
- case "expect": {
3392
+ case "expect":
3287
3393
  this.#visitAssertion(member, runMode, parentResult);
3288
3394
  break;
3289
- }
3290
3395
  }
3291
3396
  }
3292
3397
  }
@@ -3510,7 +3615,7 @@ class TSTyche {
3510
3615
  #runner;
3511
3616
  #selectService;
3512
3617
  #storeService;
3513
- static version = "3.0.0-beta.1";
3618
+ static version = "3.0.0-beta.2";
3514
3619
  constructor(resolvedConfig, outputService, selectService, storeService) {
3515
3620
  this.#resolvedConfig = resolvedConfig;
3516
3621
  this.#outputService = outputService;
@@ -4052,7 +4157,7 @@ class Cli {
4052
4157
  }
4053
4158
  const selectService = new SelectService(resolvedConfig);
4054
4159
  let testFiles = [];
4055
- if (resolvedConfig.testFileMatch.length !== 0) {
4160
+ if (resolvedConfig.testFileMatch.length > 0) {
4056
4161
  testFiles = await selectService.selectFiles();
4057
4162
  if (testFiles.length === 0) {
4058
4163
  if (commandLineArguments.includes("--watch")) {
@@ -4103,4 +4208,4 @@ class Cli {
4103
4208
  }
4104
4209
  }
4105
4210
 
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 };
4211
+ 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 };