tstyche 3.0.0-beta.4 → 3.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -132,7 +132,8 @@ declare enum OptionBrand {
132
132
 
133
133
  declare enum OptionGroup {
134
134
  CommandLine = 2,
135
- ConfigFile = 4
135
+ ConfigFile = 4,
136
+ ResolvedConfig = 6
136
137
  }
137
138
 
138
139
  declare class ConfigDiagnosticText {
@@ -146,6 +147,7 @@ declare class ConfigDiagnosticText {
146
147
  static testFileMatchCannotStartWith(segment: string): Array<string>;
147
148
  static requiresValueType(optionName: string, optionBrand: OptionBrand, optionGroup: OptionGroup): string;
148
149
  static unknownOption(optionName: string): string;
150
+ static usage(optionName: string, optionBrand: OptionBrand, optionGroup: OptionGroup): Promise<Array<string>>;
149
151
  static versionIsNotSupported(value: string): string;
150
152
  static watchCannotBeEnabled(): string;
151
153
  }
@@ -178,6 +180,10 @@ interface ConfigFileOptions {
178
180
  * The list of glob patterns matching the test files.
179
181
  */
180
182
  testFileMatch?: Array<string>;
183
+ /**
184
+ * The look up strategy to be used to find the TSConfig file.
185
+ */
186
+ tsconfig?: string;
181
187
  }
182
188
 
183
189
  /**
@@ -232,6 +238,10 @@ interface CommandLineOptions {
232
238
  * The list of TypeScript versions to be tested on.
233
239
  */
234
240
  target?: Array<string>;
241
+ /**
242
+ * The look up strategy to be used to find the TSConfig file.
243
+ */
244
+ tsconfig?: string;
235
245
  /**
236
246
  * Fetch the 'typescript' package metadata from the registry and exit.
237
247
  */
@@ -297,6 +307,8 @@ interface ListTypeOptionDefinition extends BaseOptionDefinition {
297
307
  declare class Options {
298
308
  #private;
299
309
  static for(optionGroup: OptionGroup): Map<string, OptionDefinition>;
310
+ static resolve(optionName: string, optionValue: string, rootPath?: string): string;
311
+ static validate(optionDefinition: OptionDefinition | ItemDefinition, optionValue: string, optionGroup: OptionGroup, onDiagnostics: DiagnosticsHandler$1, origin?: DiagnosticOrigin): Promise<void>;
300
312
  }
301
313
 
302
314
  declare const defaultOptions: Required<ConfigFileOptions>;
@@ -443,10 +455,10 @@ type TargetResultStatus = ResultStatus.Runs | ResultStatus.Passed | ResultStatus
443
455
  declare class TargetResult {
444
456
  results: Map<string | undefined, ProjectResult>;
445
457
  status: TargetResultStatus;
458
+ target: string;
446
459
  tasks: Array<Task>;
447
460
  timing: ResultTiming;
448
- versionTag: string;
449
- constructor(versionTag: string, tasks: Array<Task>);
461
+ constructor(target: string, tasks: Array<Task>);
450
462
  }
451
463
 
452
464
  declare class Result {
@@ -706,25 +718,6 @@ declare class ResultHandler implements EventHandler {
706
718
  on([event, payload]: Event): void;
707
719
  }
708
720
 
709
- interface Hooks {
710
- /**
711
- * Is called after configuration is resolved and allows to modify it.
712
- */
713
- config?: (resolvedConfig: ResolvedConfig) => ResolvedConfig | Promise<ResolvedConfig>;
714
- /**
715
- * Is called after test files are selected and allows to modify the list.
716
- */
717
- select?: (testFiles: Array<string>) => Array<string | URL> | Promise<Array<string | URL>>;
718
- }
719
-
720
- declare class HooksService {
721
- #private;
722
- static addHandler(hooks: Hooks): void;
723
- static call(hook: "config", resolvedConfig: ResolvedConfig): Promise<ResolvedConfig>;
724
- static call(hook: "select", testFiles: Array<string>): Promise<Array<string | URL>>;
725
- static removeHandlers(): void;
726
- }
727
-
728
721
  type InputHandler = (chunk: string) => void;
729
722
  declare class InputService {
730
723
  #private;
@@ -841,9 +834,35 @@ declare class Path {
841
834
  static resolve(...filePaths: Array<string>): string;
842
835
  }
843
836
 
837
+ interface SelectHookContext {
838
+ resolvedConfig: ResolvedConfig;
839
+ }
840
+ interface Plugin {
841
+ /**
842
+ * The name of this plugin.
843
+ */
844
+ name: string;
845
+ /**
846
+ * Is called after configuration is resolved and allows to modify it.
847
+ */
848
+ config?: (resolvedConfig: ResolvedConfig) => ResolvedConfig | Promise<ResolvedConfig>;
849
+ /**
850
+ * Is called after test files are selected and allows to modify the list.
851
+ */
852
+ select?: (this: SelectHookContext, testFiles: Array<string>) => Array<string | URL> | Promise<Array<string | URL>>;
853
+ }
854
+
855
+ type Hooks = Required<Omit<Plugin, "name">>;
856
+ declare class PluginService {
857
+ #private;
858
+ static addHandler(plugin: Plugin): void;
859
+ static call<T extends keyof Hooks>(hook: T, argument: Parameters<Hooks[T]>[0], context: ThisParameterType<Hooks[T]>): Promise<Awaited<ReturnType<Hooks[T]>>>;
860
+ static removeHandlers(): void;
861
+ }
862
+
844
863
  declare class ProjectService {
845
864
  #private;
846
- constructor(compiler: typeof ts);
865
+ constructor(resolvedConfig: ResolvedConfig, compiler: typeof ts);
847
866
  closeFile(filePath: string): void;
848
867
  getDefaultProject(filePath: string): ts.server.Project | undefined;
849
868
  getLanguageService(filePath: string): ts.LanguageService | undefined;
@@ -909,4 +928,4 @@ declare class WatchService {
909
928
  watch(cancellationToken: CancellationToken): AsyncIterable<Array<Task>>;
910
929
  }
911
930
 
912
- export { Assertion, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, type CommandLineOptions, Config, ConfigDiagnosticText, type ConfigFileOptions, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, type DiagnosticsHandler$1 as DiagnosticsHandler, type EnvironmentOptions, type Event, EventEmitter, type EventHandler, ExitCodeHandler, ExpectResult, ExpectService, type FileWatchHandler, FileWatcher, type Hooks, HooksService, type InputHandler, InputService, type ItemDefinition, Line, ListReporter, type MatchResult, OptionBrand, type OptionDefinition, OptionGroup, Options, OutputService, Path, ProjectResult, ProjectService, type Reporter, type ReporterEvent, type ResolvedConfig, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, ScribblerJsx, type ScribblerOptions, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, type TargetResultStatus, Task, TaskResult, type TaskResultStatus, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, type TypeChecker, Version, type WatchHandler, WatchReporter, WatchService, Watcher, type WatcherOptions, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
931
+ export { Assertion, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, type CommandLineOptions, Config, ConfigDiagnosticText, type ConfigFileOptions, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, type DiagnosticsHandler$1 as DiagnosticsHandler, type EnvironmentOptions, type Event, EventEmitter, type EventHandler, ExitCodeHandler, ExpectResult, ExpectService, type FileWatchHandler, FileWatcher, type InputHandler, InputService, type ItemDefinition, Line, ListReporter, type MatchResult, OptionBrand, type OptionDefinition, OptionGroup, Options, OutputService, Path, type Plugin, PluginService, ProjectResult, ProjectService, type Reporter, type ReporterEvent, type ResolvedConfig, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, ScribblerJsx, type ScribblerOptions, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, type TargetResultStatus, Task, TaskResult, type TaskResultStatus, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, type TypeChecker, Version, type WatchHandler, WatchReporter, WatchService, Watcher, type WatcherOptions, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
package/build/tstyche.js CHANGED
@@ -1,64 +1,12 @@
1
- import { writeFileSync, rmSync, existsSync, watch } from 'node:fs';
2
1
  import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { pathToFileURL, fileURLToPath } from 'node:url';
5
2
  import { createRequire } from 'node:module';
6
3
  import vm from 'node:vm';
7
4
  import os from 'node:os';
8
5
  import process from 'node:process';
6
+ import path from 'node:path';
7
+ import { writeFileSync, rmSync, existsSync, watch } from 'node:fs';
9
8
  import streamConsumers from 'node:stream/consumers';
10
-
11
- class ConfigDiagnosticText {
12
- static expected(element) {
13
- return `Expected ${element}.`;
14
- }
15
- static expectsListItemType(optionName, optionBrand) {
16
- return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
17
- }
18
- static expectsValue(optionName, optionGroup) {
19
- optionName = ConfigDiagnosticText.#optionName(optionName, optionGroup);
20
- return `Option '${optionName}' expects a value.`;
21
- }
22
- static fileDoesNotExist(filePath) {
23
- return `The specified path '${filePath}' does not exist.`;
24
- }
25
- static moduleWasNotFound(specifier) {
26
- return `The specified module '${specifier}' was not found.`;
27
- }
28
- static #optionName(optionName, optionGroup) {
29
- switch (optionGroup) {
30
- case 2:
31
- return `--${optionName}`;
32
- case 4:
33
- return optionName;
34
- }
35
- }
36
- static seen(element) {
37
- return `The ${element} was seen here.`;
38
- }
39
- static testFileMatchCannotStartWith(segment) {
40
- return [
41
- `A test file match pattern cannot start with '${segment}'.`,
42
- "The test files are only collected within the 'rootPath' directory.",
43
- ];
44
- }
45
- static requiresValueType(optionName, optionBrand, optionGroup) {
46
- optionName = ConfigDiagnosticText.#optionName(optionName, optionGroup);
47
- return `Option '${optionName}' requires a value of type ${optionBrand}.`;
48
- }
49
- static unknownOption(optionName) {
50
- return `Unknown option '${optionName}'.`;
51
- }
52
- static versionIsNotSupported(value) {
53
- if (value === "current") {
54
- return "Cannot use 'current' as a target. Failed to resolve the path to the currently installed TypeScript module.";
55
- }
56
- return `TypeScript version '${value}' is not supported.`;
57
- }
58
- static watchCannotBeEnabled() {
59
- return "Watch mode cannot be enabled in continuous integration environment.";
60
- }
61
- }
9
+ import { pathToFileURL, fileURLToPath } from 'node:url';
62
10
 
63
11
  class DiagnosticOrigin {
64
12
  assertion;
@@ -169,49 +117,6 @@ class SourceFile {
169
117
  }
170
118
  }
171
119
 
172
- class EventEmitter {
173
- static instanceCount = 0;
174
- static #handlers = new Map();
175
- static #reporters = new Map();
176
- #scope;
177
- constructor() {
178
- this.#scope = EventEmitter.instanceCount++;
179
- EventEmitter.#handlers.set(this.#scope, new Set());
180
- EventEmitter.#reporters.set(this.#scope, new Set());
181
- }
182
- addHandler(handler) {
183
- EventEmitter.#handlers.get(this.#scope)?.add(handler);
184
- }
185
- addReporter(reporter) {
186
- EventEmitter.#reporters.get(this.#scope)?.add(reporter);
187
- }
188
- static dispatch(event) {
189
- function forEachHandler(handlers, event) {
190
- for (const handler of handlers) {
191
- handler.on(event);
192
- }
193
- }
194
- for (const handlers of EventEmitter.#handlers.values()) {
195
- forEachHandler(handlers, event);
196
- }
197
- for (const handlers of EventEmitter.#reporters.values()) {
198
- forEachHandler(handlers, event);
199
- }
200
- }
201
- removeHandler(handler) {
202
- EventEmitter.#handlers.get(this.#scope)?.delete(handler);
203
- }
204
- removeReporter(reporter) {
205
- EventEmitter.#reporters.get(this.#scope)?.delete(reporter);
206
- }
207
- removeHandlers() {
208
- EventEmitter.#handlers.get(this.#scope)?.clear();
209
- }
210
- removeReporters() {
211
- EventEmitter.#reporters.get(this.#scope)?.clear();
212
- }
213
- }
214
-
215
120
  class Path {
216
121
  static normalizeSlashes;
217
122
  static {
@@ -317,6 +222,49 @@ class Environment {
317
222
 
318
223
  const environmentOptions = Environment.resolve();
319
224
 
225
+ class EventEmitter {
226
+ static instanceCount = 0;
227
+ static #handlers = new Map();
228
+ static #reporters = new Map();
229
+ #scope;
230
+ constructor() {
231
+ this.#scope = EventEmitter.instanceCount++;
232
+ EventEmitter.#handlers.set(this.#scope, new Set());
233
+ EventEmitter.#reporters.set(this.#scope, new Set());
234
+ }
235
+ addHandler(handler) {
236
+ EventEmitter.#handlers.get(this.#scope)?.add(handler);
237
+ }
238
+ addReporter(reporter) {
239
+ EventEmitter.#reporters.get(this.#scope)?.add(reporter);
240
+ }
241
+ static dispatch(event) {
242
+ function forEachHandler(handlers, event) {
243
+ for (const handler of handlers) {
244
+ handler.on(event);
245
+ }
246
+ }
247
+ for (const handlers of EventEmitter.#handlers.values()) {
248
+ forEachHandler(handlers, event);
249
+ }
250
+ for (const handlers of EventEmitter.#reporters.values()) {
251
+ forEachHandler(handlers, event);
252
+ }
253
+ }
254
+ removeHandler(handler) {
255
+ EventEmitter.#handlers.get(this.#scope)?.delete(handler);
256
+ }
257
+ removeReporter(reporter) {
258
+ EventEmitter.#reporters.get(this.#scope)?.delete(reporter);
259
+ }
260
+ removeHandlers() {
261
+ EventEmitter.#handlers.get(this.#scope)?.clear();
262
+ }
263
+ removeReporters() {
264
+ EventEmitter.#reporters.get(this.#scope)?.clear();
265
+ }
266
+ }
267
+
320
268
  class Version {
321
269
  static isGreaterThan(source, target) {
322
270
  return !(source === target) && Version.#satisfies(source, target);
@@ -809,86 +757,73 @@ class Store {
809
757
  }
810
758
  }
811
759
 
812
- class OptionUsageText {
813
- #optionGroup;
814
- constructor(optionGroup) {
815
- this.#optionGroup = optionGroup;
760
+ class ConfigDiagnosticText {
761
+ static expected(element) {
762
+ return `Expected ${element}.`;
816
763
  }
817
- async get(optionName, optionBrand) {
818
- const usageText = [];
764
+ static expectsListItemType(optionName, optionBrand) {
765
+ return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
766
+ }
767
+ static expectsValue(optionName, optionGroup) {
768
+ optionName = ConfigDiagnosticText.#optionName(optionName, optionGroup);
769
+ return `Option '${optionName}' expects a value.`;
770
+ }
771
+ static fileDoesNotExist(filePath) {
772
+ return `The specified path '${filePath}' does not exist.`;
773
+ }
774
+ static moduleWasNotFound(specifier) {
775
+ return `The specified module '${specifier}' was not found.`;
776
+ }
777
+ static #optionName(optionName, optionGroup) {
778
+ return optionGroup === 2 ? `--${optionName}` : optionName;
779
+ }
780
+ static seen(element) {
781
+ return `The ${element} was seen here.`;
782
+ }
783
+ static testFileMatchCannotStartWith(segment) {
784
+ return [
785
+ `A test file match pattern cannot start with '${segment}'.`,
786
+ "The test files are only collected within the 'rootPath' directory.",
787
+ ];
788
+ }
789
+ static requiresValueType(optionName, optionBrand, optionGroup) {
790
+ const optionNameText = ConfigDiagnosticText.#optionName(optionName, optionGroup);
791
+ return `Option '${optionNameText}' requires a value of type ${optionBrand}.`;
792
+ }
793
+ static unknownOption(optionName) {
794
+ return `Unknown option '${optionName}'.`;
795
+ }
796
+ static async usage(optionName, optionBrand, optionGroup) {
797
+ const text = [];
819
798
  switch (optionName) {
820
799
  case "target": {
821
- switch (this.#optionGroup) {
800
+ switch (optionGroup) {
822
801
  case 2:
823
- 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'.");
802
+ text.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'.");
824
803
  break;
825
804
  case 4:
826
- usageText.push("Item of the 'target' list must be a supported version tag.");
805
+ text.push("Item of the 'target' list must be a supported version tag.");
827
806
  break;
828
807
  }
829
808
  const supportedTags = await Store.getSupportedTags();
830
809
  if (supportedTags != null) {
831
- usageText.push(`Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`);
810
+ text.push(`Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`);
832
811
  }
833
812
  break;
834
813
  }
835
814
  default:
836
- usageText.push(ConfigDiagnosticText.requiresValueType(optionName, optionBrand, this.#optionGroup));
815
+ text.push(ConfigDiagnosticText.requiresValueType(optionName, optionBrand, optionGroup));
837
816
  }
838
- return usageText;
839
- }
840
- }
841
-
842
- class OptionValidator {
843
- #onDiagnostics;
844
- #optionGroup;
845
- #optionUsageText;
846
- constructor(optionGroup, onDiagnostics) {
847
- this.#optionGroup = optionGroup;
848
- this.#onDiagnostics = onDiagnostics;
849
- this.#optionUsageText = new OptionUsageText(this.#optionGroup);
817
+ return text;
850
818
  }
851
- async check(optionName, optionValue, optionBrand, origin) {
852
- switch (optionName) {
853
- case "config":
854
- case "rootPath":
855
- if (!existsSync(optionValue)) {
856
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
857
- }
858
- break;
859
- case "reporters":
860
- if (["list", "summary"].includes(optionValue)) {
861
- break;
862
- }
863
- case "plugins":
864
- try {
865
- await import(optionValue);
866
- }
867
- catch {
868
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.moduleWasNotFound(optionValue), origin));
869
- }
870
- break;
871
- case "target":
872
- if ((await Store.validateTag(optionValue)) === false) {
873
- this.#onDiagnostics(Diagnostic.error([
874
- ConfigDiagnosticText.versionIsNotSupported(optionValue),
875
- ...(await this.#optionUsageText.get(optionName, optionBrand)),
876
- ], origin));
877
- }
878
- break;
879
- case "testFileMatch":
880
- for (const segment of ["/", "../"]) {
881
- if (optionValue.startsWith(segment)) {
882
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
883
- }
884
- }
885
- break;
886
- case "watch":
887
- if (environmentOptions.isCi) {
888
- this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
889
- }
890
- break;
819
+ static versionIsNotSupported(value) {
820
+ if (value === "current") {
821
+ return "Cannot use 'current' as a target. Failed to resolve the path to the currently installed TypeScript module.";
891
822
  }
823
+ return `TypeScript version '${value}' is not supported.`;
824
+ }
825
+ static watchCannotBeEnabled() {
826
+ return "Watch mode cannot be enabled in continuous integration environment.";
892
827
  }
893
828
  }
894
829
 
@@ -1001,6 +936,12 @@ class Options {
1001
936
  },
1002
937
  name: "testFileMatch",
1003
938
  },
939
+ {
940
+ brand: "string",
941
+ description: "The look up strategy to be used to find the TSConfig file.",
942
+ group: 2 | 4,
943
+ name: "tsconfig",
944
+ },
1004
945
  {
1005
946
  brand: "bareTrue",
1006
947
  description: "Fetch the 'typescript' package metadata from the registry and exit.",
@@ -1029,29 +970,89 @@ class Options {
1029
970
  }
1030
971
  return definitionMap;
1031
972
  }
973
+ static resolve(optionName, optionValue, rootPath = ".") {
974
+ switch (optionName) {
975
+ case "tsconfig":
976
+ if (["findup", "ignore"].includes(optionValue)) {
977
+ break;
978
+ }
979
+ case "config":
980
+ case "rootPath":
981
+ optionValue = Path.resolve(rootPath, optionValue);
982
+ break;
983
+ case "plugins":
984
+ case "reporters":
985
+ if (optionValue.startsWith(".")) {
986
+ optionValue = pathToFileURL(Path.join(Path.relative(".", rootPath), optionValue)).toString();
987
+ }
988
+ break;
989
+ }
990
+ return optionValue;
991
+ }
992
+ static async validate(optionDefinition, optionValue, optionGroup, onDiagnostics, origin) {
993
+ switch (optionDefinition.name) {
994
+ case "tsconfig":
995
+ if (["findup", "ignore"].includes(optionValue)) {
996
+ break;
997
+ }
998
+ case "config":
999
+ case "rootPath":
1000
+ if (!existsSync(optionValue)) {
1001
+ onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
1002
+ }
1003
+ break;
1004
+ case "reporters":
1005
+ if (["list", "summary"].includes(optionValue)) {
1006
+ break;
1007
+ }
1008
+ case "plugins":
1009
+ try {
1010
+ await import(optionValue);
1011
+ }
1012
+ catch {
1013
+ onDiagnostics(Diagnostic.error(ConfigDiagnosticText.moduleWasNotFound(optionValue), origin));
1014
+ }
1015
+ break;
1016
+ case "target":
1017
+ if ((await Store.validateTag(optionValue)) === false) {
1018
+ onDiagnostics(Diagnostic.error([
1019
+ ConfigDiagnosticText.versionIsNotSupported(optionValue),
1020
+ await ConfigDiagnosticText.usage(optionDefinition.name, optionDefinition.brand, optionGroup),
1021
+ ].flat(), origin));
1022
+ }
1023
+ break;
1024
+ case "testFileMatch":
1025
+ for (const segment of ["/", "../"]) {
1026
+ if (optionValue.startsWith(segment)) {
1027
+ onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
1028
+ }
1029
+ }
1030
+ break;
1031
+ case "watch":
1032
+ if (environmentOptions.isCi) {
1033
+ onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
1034
+ }
1035
+ break;
1036
+ }
1037
+ }
1032
1038
  }
1033
1039
 
1034
1040
  class CommandLineParser {
1035
1041
  #commandLineOptions;
1036
1042
  #onDiagnostics;
1037
1043
  #options;
1038
- #optionGroup = 2;
1039
- #optionUsageText;
1040
- #optionValidator;
1041
1044
  #pathMatch;
1042
1045
  constructor(commandLine, pathMatch, onDiagnostics) {
1043
1046
  this.#commandLineOptions = commandLine;
1044
1047
  this.#pathMatch = pathMatch;
1045
1048
  this.#onDiagnostics = onDiagnostics;
1046
- this.#options = Options.for(this.#optionGroup);
1047
- this.#optionUsageText = new OptionUsageText(this.#optionGroup);
1048
- this.#optionValidator = new OptionValidator(this.#optionGroup, this.#onDiagnostics);
1049
+ this.#options = Options.for(2);
1049
1050
  }
1050
1051
  async #onExpectsValue(optionDefinition) {
1051
1052
  const text = [
1052
- ConfigDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
1053
- ...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
1054
- ];
1053
+ ConfigDiagnosticText.expectsValue(optionDefinition.name, 2),
1054
+ await ConfigDiagnosticText.usage(optionDefinition.name, optionDefinition.brand, 2),
1055
+ ].flat();
1055
1056
  this.#onDiagnostics(Diagnostic.error(text));
1056
1057
  }
1057
1058
  async parse(commandLineArgs) {
@@ -1082,11 +1083,11 @@ class CommandLineParser {
1082
1083
  let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
1083
1084
  switch (optionDefinition.brand) {
1084
1085
  case "bareTrue":
1085
- await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
1086
+ await Options.validate(optionDefinition, optionValue, 2, this.#onDiagnostics);
1086
1087
  this.#commandLineOptions[optionDefinition.name] = true;
1087
1088
  break;
1088
1089
  case "boolean":
1089
- await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
1090
+ await Options.validate(optionDefinition, optionValue, 2, this.#onDiagnostics);
1090
1091
  this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
1091
1092
  if (optionValue === "false" || optionValue === "true") {
1092
1093
  index++;
@@ -1094,20 +1095,13 @@ class CommandLineParser {
1094
1095
  break;
1095
1096
  case "list":
1096
1097
  if (optionValue !== "") {
1097
- let optionValues = optionValue
1098
+ const optionValues = optionValue
1098
1099
  .split(",")
1099
1100
  .map((value) => value.trim())
1100
- .filter((value) => value !== "");
1101
- if (optionDefinition.name === "plugins" || optionDefinition.name === "reporters") {
1102
- optionValues = optionValues.map((optionValue) => {
1103
- if (optionValue.startsWith(".")) {
1104
- return pathToFileURL(optionValue).toString();
1105
- }
1106
- return optionValue;
1107
- });
1108
- }
1101
+ .filter((value) => value !== "")
1102
+ .map((value) => Options.resolve(optionDefinition.name, value));
1109
1103
  for (const optionValue of optionValues) {
1110
- await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
1104
+ await Options.validate(optionDefinition, optionValue, 2, this.#onDiagnostics);
1111
1105
  }
1112
1106
  this.#commandLineOptions[optionDefinition.name] = optionValues;
1113
1107
  index++;
@@ -1117,10 +1111,8 @@ class CommandLineParser {
1117
1111
  break;
1118
1112
  case "string":
1119
1113
  if (optionValue !== "") {
1120
- if (optionDefinition.name === "config") {
1121
- optionValue = Path.resolve(optionValue);
1122
- }
1123
- await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
1114
+ optionValue = Options.resolve(optionDefinition.name, optionValue);
1115
+ await Options.validate(optionDefinition, optionValue, 2, this.#onDiagnostics);
1124
1116
  this.#commandLineOptions[optionDefinition.name] = optionValue;
1125
1117
  index++;
1126
1118
  break;
@@ -1268,21 +1260,18 @@ class ConfigFileParser {
1268
1260
  #jsonScanner;
1269
1261
  #onDiagnostics;
1270
1262
  #options;
1271
- #optionGroup = 4;
1272
- #optionValidator;
1273
1263
  #sourceFile;
1274
1264
  constructor(configFileOptions, sourceFile, onDiagnostics) {
1275
1265
  this.#configFileOptions = configFileOptions;
1276
1266
  this.#sourceFile = sourceFile;
1277
1267
  this.#onDiagnostics = onDiagnostics;
1278
- this.#options = Options.for(this.#optionGroup);
1268
+ this.#options = Options.for(4);
1279
1269
  this.#jsonScanner = new JsonScanner(this.#sourceFile);
1280
- this.#optionValidator = new OptionValidator(this.#optionGroup, this.#onDiagnostics);
1281
1270
  }
1282
1271
  #onRequiresValue(optionDefinition, jsonNode, isListItem) {
1283
1272
  const text = isListItem
1284
1273
  ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
1285
- : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
1274
+ : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, 4);
1286
1275
  this.#onDiagnostics(Diagnostic.error(text, jsonNode.origin));
1287
1276
  }
1288
1277
  async #parseValue(optionDefinition, isListItem = false) {
@@ -1305,16 +1294,9 @@ class ConfigFileParser {
1305
1294
  this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1306
1295
  break;
1307
1296
  }
1308
- if (optionDefinition.name === "plugins" || optionDefinition.name === "reporters") {
1309
- if (optionValue.startsWith(".")) {
1310
- const configFilePath = Path.relative(".", Path.dirname(this.#sourceFile.fileName));
1311
- optionValue = pathToFileURL(Path.join(configFilePath, optionValue)).toString();
1312
- }
1313
- }
1314
- if (optionDefinition.name === "rootPath") {
1315
- optionValue = Path.resolve(Path.dirname(this.#sourceFile.fileName), optionValue);
1316
- }
1317
- await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand, jsonNode.origin);
1297
+ const rootPath = Path.dirname(this.#sourceFile.fileName);
1298
+ optionValue = Options.resolve(optionDefinition.name, optionValue, rootPath);
1299
+ await Options.validate(optionDefinition, optionValue, 4, this.#onDiagnostics, jsonNode.origin);
1318
1300
  break;
1319
1301
  }
1320
1302
  case "list": {
@@ -1420,6 +1402,7 @@ const defaultOptions = {
1420
1402
  rootPath: Path.resolve("./"),
1421
1403
  target: environmentOptions.typescriptPath != null ? ["current"] : ["latest"],
1422
1404
  testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
1405
+ tsconfig: "findup",
1423
1406
  };
1424
1407
 
1425
1408
  class Config {
@@ -1479,6 +1462,7 @@ var OptionGroup;
1479
1462
  (function (OptionGroup) {
1480
1463
  OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
1481
1464
  OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
1465
+ OptionGroup[OptionGroup["ResolvedConfig"] = 6] = "ResolvedConfig";
1482
1466
  })(OptionGroup || (OptionGroup = {}));
1483
1467
 
1484
1468
  class CancellationHandler {
@@ -1596,11 +1580,11 @@ var ResultStatus;
1596
1580
  class TargetResult {
1597
1581
  results = new Map();
1598
1582
  status = "runs";
1583
+ target;
1599
1584
  tasks;
1600
1585
  timing = new ResultTiming();
1601
- versionTag;
1602
- constructor(versionTag, tasks) {
1603
- this.versionTag = versionTag;
1586
+ constructor(target, tasks) {
1587
+ this.target = target;
1604
1588
  this.tasks = tasks;
1605
1589
  }
1606
1590
  }
@@ -1824,25 +1808,6 @@ class ResultHandler {
1824
1808
  }
1825
1809
  }
1826
1810
 
1827
- class HooksService {
1828
- static #handlers = new Set();
1829
- static addHandler(hooks) {
1830
- HooksService.#handlers.add(hooks);
1831
- }
1832
- static async call(hook, options) {
1833
- for (const handler of HooksService.#handlers) {
1834
- const result = await handler[hook]?.(options);
1835
- if (result != null) {
1836
- options = result;
1837
- }
1838
- }
1839
- return options;
1840
- }
1841
- static removeHandlers() {
1842
- HooksService.#handlers.clear();
1843
- }
1844
- }
1845
-
1846
1811
  function jsx(type, props) {
1847
1812
  return { props, type };
1848
1813
  }
@@ -2224,6 +2189,25 @@ function watchUsageText() {
2224
2189
  return jsx(Text, { children: usageText });
2225
2190
  }
2226
2191
 
2192
+ class PluginService {
2193
+ static #handlers = new Map();
2194
+ static addHandler(plugin) {
2195
+ PluginService.#handlers.set(plugin.name, plugin);
2196
+ }
2197
+ static async call(hook, argument, context) {
2198
+ let result = argument;
2199
+ for (const [, plugin] of PluginService.#handlers) {
2200
+ if (hook in plugin) {
2201
+ result = await plugin[hook].call(context, result);
2202
+ }
2203
+ }
2204
+ return result;
2205
+ }
2206
+ static removeHandlers() {
2207
+ PluginService.#handlers.clear();
2208
+ }
2209
+ }
2210
+
2227
2211
  class BaseReporter {
2228
2212
  resolvedConfig;
2229
2213
  constructor(resolvedConfig) {
@@ -2460,7 +2444,7 @@ class Task {
2460
2444
  filePath;
2461
2445
  position;
2462
2446
  constructor(filePath, position) {
2463
- this.filePath = Path.normalizeSlashes(this.#toPath(filePath));
2447
+ this.filePath = Path.resolve(this.#toPath(filePath));
2464
2448
  this.position = position;
2465
2449
  }
2466
2450
  #toPath(filePath) {
@@ -3087,8 +3071,10 @@ var TestMemberFlags;
3087
3071
 
3088
3072
  class ProjectService {
3089
3073
  #compiler;
3074
+ #resolvedConfig;
3090
3075
  #service;
3091
- constructor(compiler) {
3076
+ constructor(resolvedConfig, compiler) {
3077
+ this.#resolvedConfig = resolvedConfig;
3092
3078
  this.#compiler = compiler;
3093
3079
  const noop = () => undefined;
3094
3080
  const noopLogger = {
@@ -3123,6 +3109,15 @@ class ProjectService {
3123
3109
  useInferredProjectPerProjectRoot: true,
3124
3110
  useSingleInferredProject: false,
3125
3111
  });
3112
+ switch (this.#resolvedConfig.tsconfig) {
3113
+ case "findup":
3114
+ break;
3115
+ case "ignore":
3116
+ this.#service.getConfigFileNameForFile = () => undefined;
3117
+ break;
3118
+ default:
3119
+ this.#service.getConfigFileNameForFile = () => this.#resolvedConfig.tsconfig;
3120
+ }
3126
3121
  this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
3127
3122
  }
3128
3123
  closeFile(filePath) {
@@ -3757,7 +3752,7 @@ class ToRaiseError {
3757
3752
  if (this.#compiler.isStringLiteralLike(targetNode)) {
3758
3753
  return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
3759
3754
  }
3760
- return Number.parseInt(targetNode.text) === diagnostic.code;
3755
+ return Number.parseInt(targetNode.text, 10) === diagnostic.code;
3761
3756
  }
3762
3757
  }
3763
3758
 
@@ -4038,7 +4033,7 @@ class TaskRunner {
4038
4033
  this.#resolvedConfig = resolvedConfig;
4039
4034
  this.#compiler = compiler;
4040
4035
  this.#collectService = new CollectService(compiler);
4041
- this.#projectService = new ProjectService(compiler);
4036
+ this.#projectService = new ProjectService(this.#resolvedConfig, compiler);
4042
4037
  }
4043
4038
  run(task, cancellationToken) {
4044
4039
  if (cancellationToken?.isCancellationRequested === true) {
@@ -4052,6 +4047,13 @@ class TaskRunner {
4052
4047
  this.#projectService.closeFile(task.filePath);
4053
4048
  }
4054
4049
  #run(task, taskResult, cancellationToken) {
4050
+ if (!existsSync(task.filePath)) {
4051
+ EventEmitter.dispatch([
4052
+ "task:error",
4053
+ { diagnostics: [Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], result: taskResult },
4054
+ ]);
4055
+ return;
4056
+ }
4055
4057
  const languageService = this.#projectService.getLanguageService(task.filePath);
4056
4058
  if (!languageService) {
4057
4059
  return;
@@ -4095,7 +4097,7 @@ class TaskRunner {
4095
4097
  class Runner {
4096
4098
  #eventEmitter = new EventEmitter();
4097
4099
  #resolvedConfig;
4098
- static version = "3.0.0-beta.4";
4100
+ static version = "3.0.0-beta.6";
4099
4101
  constructor(resolvedConfig) {
4100
4102
  this.#resolvedConfig = resolvedConfig;
4101
4103
  }
@@ -4143,10 +4145,10 @@ class Runner {
4143
4145
  async #run(tasks, cancellationToken) {
4144
4146
  const result = new Result(this.#resolvedConfig, tasks);
4145
4147
  EventEmitter.dispatch(["run:start", { result }]);
4146
- for (const versionTag of this.#resolvedConfig.target) {
4147
- const targetResult = new TargetResult(versionTag, tasks);
4148
+ for (const target of this.#resolvedConfig.target) {
4149
+ const targetResult = new TargetResult(target, tasks);
4148
4150
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
4149
- const compiler = await Store.load(versionTag);
4151
+ const compiler = await Store.load(target);
4150
4152
  if (compiler) {
4151
4153
  const taskRunner = new TaskRunner(this.#resolvedConfig, compiler);
4152
4154
  for (const task of tasks) {
@@ -4219,11 +4221,11 @@ class Cli {
4219
4221
  }
4220
4222
  continue;
4221
4223
  }
4222
- for (const plugin of resolvedConfig.plugins) {
4223
- const hooks = (await import(plugin)).default;
4224
- HooksService.addHandler(hooks);
4224
+ for (const pluginIdentifier of resolvedConfig.plugins) {
4225
+ const plugin = (await import(pluginIdentifier)).default;
4226
+ PluginService.addHandler(plugin);
4225
4227
  }
4226
- resolvedConfig = await HooksService.call("config", resolvedConfig);
4228
+ resolvedConfig = await PluginService.call("config", resolvedConfig, {});
4227
4229
  if (commandLine.includes("--showConfig")) {
4228
4230
  OutputService.writeMessage(formattedText({ ...resolvedConfig, ...environmentOptions }));
4229
4231
  continue;
@@ -4244,7 +4246,7 @@ class Cli {
4244
4246
  continue;
4245
4247
  }
4246
4248
  }
4247
- testFiles = await HooksService.call("select", testFiles);
4249
+ testFiles = await PluginService.call("select", testFiles, { resolvedConfig });
4248
4250
  if (commandLine.includes("--listFiles")) {
4249
4251
  OutputService.writeMessage(formattedText(testFiles.map((testFile) => testFile.toString())));
4250
4252
  continue;
@@ -4253,7 +4255,7 @@ class Cli {
4253
4255
  this.#eventEmitter.removeReporter(setupReporter);
4254
4256
  const runner = new Runner(resolvedConfig);
4255
4257
  await runner.run(testFiles, cancellationToken);
4256
- HooksService.removeHandlers();
4258
+ PluginService.removeHandlers();
4257
4259
  } while (cancellationToken.reason === "configChange");
4258
4260
  this.#eventEmitter.removeHandlers();
4259
4261
  }
@@ -4285,4 +4287,4 @@ class Cli {
4285
4287
  }
4286
4288
  }
4287
4289
 
4288
- export { Assertion, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, HooksService, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, 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 };
4290
+ export { Assertion, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "3.0.0-beta.4",
3
+ "version": "3.0.0-beta.6",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -61,16 +61,16 @@
61
61
  "test:unit": "yarn test:run **/__tests__/*.test.js --parallel"
62
62
  },
63
63
  "devDependencies": {
64
- "@biomejs/biome": "1.9.3",
64
+ "@biomejs/biome": "1.9.4",
65
65
  "@rollup/plugin-typescript": "12.1.1",
66
- "@types/node": "20.16.11",
67
- "@types/react": "18.3.11",
66
+ "@types/node": "20.17.1",
67
+ "@types/react": "18.3.12",
68
68
  "ajv": "8.17.1",
69
- "cspell": "8.15.3",
69
+ "cspell": "8.15.4",
70
70
  "magic-string": "0.30.12",
71
71
  "monocart-coverage-reports": "2.11.1",
72
72
  "pretty-ansi": "2.0.0",
73
- "rollup": "4.24.0",
73
+ "rollup": "4.24.2",
74
74
  "rollup-plugin-dts": "6.1.1",
75
75
  "tslib": "2.8.0",
76
76
  "typescript": "5.6.3"
@@ -83,7 +83,7 @@
83
83
  "optional": true
84
84
  }
85
85
  },
86
- "packageManager": "yarn@4.5.0",
86
+ "packageManager": "yarn@4.5.1",
87
87
  "engines": {
88
88
  "node": ">=18.17"
89
89
  }