tstyche 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,10 +64,10 @@ Here is the list of all matchers:
64
64
 
65
65
  ## Runner
66
66
 
67
- The `tstyche` command is the heart of TSTyche. For example, it can select test files by path, filter tests by name and pass them through TypeScript `4.8` and `latest`:
67
+ The `tstyche` command is the heart of TSTyche. For example, it can select test files by path, filter tests by name and pass them through a range of TypeScript versions:
68
68
 
69
- ```sh
70
- tstyche JsonObject --only external --target 4.8,latest
69
+ ```shell
70
+ tstyche query-params --only multiple --target '>=5.0 <5.3'
71
71
  ```
72
72
 
73
73
  This simple! (And it has watch mode too.)
@@ -152,6 +152,10 @@ declare class ConfigDiagnosticText {
152
152
  * Options loaded from the configuration file.
153
153
  */
154
154
  interface ConfigFileOptions {
155
+ /**
156
+ * Enable type error reporting for source files.
157
+ */
158
+ checkSourceFiles?: boolean;
155
159
  /**
156
160
  * Stop running tests after the first failed assertion.
157
161
  */
@@ -867,4 +871,4 @@ declare class WatchService {
867
871
  watch(cancellationToken: CancellationToken): AsyncIterable<Array<Task>>;
868
872
  }
869
873
 
870
- export { Assertion, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, type CommandLineOptions, Config, ConfigDiagnosticText, type ConfigFileOptions, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, type 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 };
874
+ export { Assertion, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, type CommandLineOptions, Config, ConfigDiagnosticText, type ConfigFileOptions, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, type 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, type SelectHookContext, 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
@@ -824,13 +824,14 @@ class Store {
824
824
 
825
825
  class Target {
826
826
  static #rangeRegex = /^[<>]=?\d\.\d( [<>]=?\d\.\d)?$/;
827
- static expand(queries) {
827
+ static async expand(queries) {
828
828
  const include = [];
829
829
  for (const query of queries) {
830
830
  if (!Target.isRange(query)) {
831
831
  include.push(query);
832
832
  continue;
833
833
  }
834
+ await Store.open();
834
835
  if (Store.manifest != null) {
835
836
  let versions = Object.keys(Store.manifest.resolutions).slice(0, -4);
836
837
  for (const comparator of query.split(" ")) {
@@ -842,14 +843,16 @@ class Target {
842
843
  return include;
843
844
  }
844
845
  static #filter(comparator, versions) {
845
- const targetVersionIndex = versions.findIndex((version) => version === comparator.replace(/^[<>]=?/, ""));
846
- if (targetVersionIndex !== -1) {
847
- switch (comparator.charAt(0)) {
848
- case ">":
849
- return versions.slice(comparator.charAt(1) === "=" ? targetVersionIndex : targetVersionIndex + 1);
850
- case "<":
851
- return versions.slice(0, comparator.charAt(1) === "=" ? targetVersionIndex + 1 : targetVersionIndex);
852
- }
846
+ const targetVersion = comparator.replace(/^[<>]=?/, "");
847
+ switch (comparator.charAt(0)) {
848
+ case ">":
849
+ return versions.filter((sourceVersion) => comparator.charAt(1) === "="
850
+ ? Version.isSatisfiedWith(sourceVersion, targetVersion)
851
+ : Version.isGreaterThan(sourceVersion, targetVersion));
852
+ case "<":
853
+ return versions.filter((sourceVersion) => comparator.charAt(1) === "="
854
+ ? Version.isSatisfiedWith(targetVersion, sourceVersion)
855
+ : Version.isGreaterThan(targetVersion, sourceVersion));
853
856
  }
854
857
  return [];
855
858
  }
@@ -866,6 +869,12 @@ class Options {
866
869
  group: 4,
867
870
  name: "$schema",
868
871
  },
872
+ {
873
+ brand: "boolean",
874
+ description: "Enable type error reporting for source files.",
875
+ group: 4,
876
+ name: "checkSourceFiles",
877
+ },
869
878
  {
870
879
  brand: "string",
871
880
  description: "The path to a TSTyche configuration file.",
@@ -1083,18 +1092,7 @@ class Options {
1083
1092
  break;
1084
1093
  case "target": {
1085
1094
  if (/[<>=]/.test(optionValue)) {
1086
- if (Target.isRange(optionValue)) {
1087
- for (const value of optionValue.split(" ").map((value) => value.replace(/^[<>]=?/, ""))) {
1088
- if ((await Store.validateTag(value)) === false) {
1089
- onDiagnostics(Diagnostic.error([
1090
- ConfigDiagnosticText.versionIsNotSupported(value),
1091
- ...ConfigDiagnosticText.usage(optionName, optionBrand),
1092
- ConfigDiagnosticText.inspectSupportedVersions(),
1093
- ], origin));
1094
- }
1095
- }
1096
- }
1097
- else {
1095
+ if (!Target.isRange(optionValue)) {
1098
1096
  onDiagnostics(Diagnostic.error([ConfigDiagnosticText.rangeIsNotValid(optionValue), ...ConfigDiagnosticText.rangeUsage()], origin));
1099
1097
  }
1100
1098
  break;
@@ -1482,6 +1480,7 @@ class ConfigFileParser {
1482
1480
  }
1483
1481
 
1484
1482
  const defaultOptions = {
1483
+ checkSourceFiles: false,
1485
1484
  failFast: false,
1486
1485
  plugins: [],
1487
1486
  rejectAnyType: false,
@@ -1502,6 +1501,9 @@ class Config {
1502
1501
  const pathMatch = [];
1503
1502
  const commandLineParser = new CommandLineParser(commandLineOptions, pathMatch, Config.#onDiagnostics);
1504
1503
  await commandLineParser.parse(commandLine);
1504
+ if (commandLineOptions.target != null) {
1505
+ commandLineOptions.target = await Target.expand(commandLineOptions.target);
1506
+ }
1505
1507
  return { commandLineOptions, pathMatch };
1506
1508
  }
1507
1509
  static async parseConfigFile(filePath) {
@@ -1516,6 +1518,9 @@ class Config {
1516
1518
  const sourceFile = new SourceFile(configFilePath, configFileText);
1517
1519
  const configFileParser = new ConfigFileParser(configFileOptions, sourceFile, Config.#onDiagnostics);
1518
1520
  await configFileParser.parse();
1521
+ if (configFileOptions.target != null) {
1522
+ configFileOptions.target = await Target.expand(configFileOptions.target);
1523
+ }
1519
1524
  }
1520
1525
  return { configFileOptions, configFilePath };
1521
1526
  }
@@ -1530,7 +1535,6 @@ class Config {
1530
1535
  if ("config" in resolvedConfig) {
1531
1536
  delete resolvedConfig.config;
1532
1537
  }
1533
- resolvedConfig.target = Target.expand(resolvedConfig.target);
1534
1538
  return resolvedConfig;
1535
1539
  }
1536
1540
  static resolveConfigFilePath(filePath) {
@@ -2338,12 +2342,11 @@ class FileView {
2338
2342
  }
2339
2343
 
2340
2344
  class ListReporter extends BaseReporter {
2341
- #currentCompilerVersion;
2342
- #currentProjectConfigFilePath;
2343
2345
  #fileCount = 0;
2344
2346
  #fileView = new FileView();
2345
2347
  #hasReportedAdds = false;
2346
2348
  #hasReportedError = false;
2349
+ #hasReportedUses = false;
2347
2350
  #isFileViewExpanded = false;
2348
2351
  #seenDeprecations = new Set();
2349
2352
  get #isLastFile() {
@@ -2374,21 +2377,14 @@ class ListReporter extends BaseReporter {
2374
2377
  break;
2375
2378
  case "target:start":
2376
2379
  this.#fileCount = payload.result.tasks.length;
2377
- break;
2378
- case "target:end":
2379
- this.#currentCompilerVersion = undefined;
2380
- this.#currentProjectConfigFilePath = undefined;
2380
+ this.#hasReportedUses = false;
2381
2381
  break;
2382
2382
  case "project:uses":
2383
- if (this.#currentCompilerVersion !== payload.compilerVersion ||
2384
- this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
2385
- OutputService.writeMessage(usesCompilerText(payload.compilerVersion, payload.projectConfigFilePath, {
2386
- prependEmptyLine: this.#currentCompilerVersion != null && !this.#hasReportedAdds && !this.#hasReportedError,
2387
- }));
2388
- this.#hasReportedAdds = false;
2389
- this.#currentCompilerVersion = payload.compilerVersion;
2390
- this.#currentProjectConfigFilePath = payload.projectConfigFilePath;
2391
- }
2383
+ OutputService.writeMessage(usesCompilerText(payload.compilerVersion, payload.projectConfigFilePath, {
2384
+ prependEmptyLine: this.#hasReportedUses && !this.#hasReportedAdds && !this.#hasReportedError,
2385
+ }));
2386
+ this.#hasReportedAdds = false;
2387
+ this.#hasReportedUses = true;
2392
2388
  break;
2393
2389
  case "project:error":
2394
2390
  for (const diagnostic of payload.diagnostics) {
@@ -3165,7 +3161,9 @@ var TestMemberFlags;
3165
3161
 
3166
3162
  class ProjectService {
3167
3163
  #compiler;
3164
+ #lastSeenProject = "";
3168
3165
  #resolvedConfig;
3166
+ #seenPrograms = new WeakSet();
3169
3167
  #service;
3170
3168
  constructor(resolvedConfig, compiler) {
3171
3169
  this.#resolvedConfig = resolvedConfig;
@@ -3240,7 +3238,12 @@ class ProjectService {
3240
3238
  return defaultCompilerOptions;
3241
3239
  }
3242
3240
  getDefaultProject(filePath) {
3243
- return this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
3241
+ const project = this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
3242
+ const compilerOptions = project?.getCompilerOptions();
3243
+ if (this.#resolvedConfig.checkSourceFiles && compilerOptions?.skipLibCheck) {
3244
+ project?.setCompilerOptions({ ...compilerOptions, skipLibCheck: false });
3245
+ }
3246
+ return project;
3244
3247
  }
3245
3248
  getLanguageService(filePath) {
3246
3249
  const project = this.getDefaultProject(filePath);
@@ -3248,16 +3251,47 @@ class ProjectService {
3248
3251
  }
3249
3252
  openFile(filePath, sourceText, projectRootPath) {
3250
3253
  const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
3251
- EventEmitter.dispatch([
3252
- "project:uses",
3253
- { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
3254
- ]);
3254
+ if (configFileName !== this.#lastSeenProject) {
3255
+ this.#lastSeenProject = configFileName;
3256
+ EventEmitter.dispatch([
3257
+ "project:uses",
3258
+ { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
3259
+ ]);
3260
+ }
3255
3261
  if (configFileErrors && configFileErrors.length > 0) {
3256
3262
  EventEmitter.dispatch([
3257
3263
  "project:error",
3258
3264
  { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.#compiler) },
3259
3265
  ]);
3260
3266
  }
3267
+ if (this.#resolvedConfig.checkSourceFiles) {
3268
+ const languageService = this.getLanguageService(filePath);
3269
+ const program = languageService?.getProgram();
3270
+ if (!program || this.#seenPrograms.has(program)) {
3271
+ return;
3272
+ }
3273
+ this.#seenPrograms.add(program);
3274
+ const filesToCheck = [];
3275
+ for (const sourceFile of program.getSourceFiles()) {
3276
+ if (program.isSourceFileFromExternalLibrary(sourceFile) || program.isSourceFileDefaultLibrary(sourceFile)) {
3277
+ continue;
3278
+ }
3279
+ if (!Select.isTestFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
3280
+ filesToCheck.push(sourceFile);
3281
+ }
3282
+ }
3283
+ const diagnostics = [];
3284
+ for (const sourceFile of filesToCheck) {
3285
+ diagnostics.push(...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile));
3286
+ }
3287
+ if (diagnostics.length > 0) {
3288
+ EventEmitter.dispatch([
3289
+ "project:error",
3290
+ { diagnostics: Diagnostic.fromDiagnostics(diagnostics, this.#compiler) },
3291
+ ]);
3292
+ return;
3293
+ }
3294
+ }
3261
3295
  }
3262
3296
  }
3263
3297
 
@@ -4273,7 +4307,7 @@ class TaskRunner {
4273
4307
  class Runner {
4274
4308
  #eventEmitter = new EventEmitter();
4275
4309
  #resolvedConfig;
4276
- static version = "3.1.0";
4310
+ static version = "3.2.0";
4277
4311
  constructor(resolvedConfig) {
4278
4312
  this.#resolvedConfig = resolvedConfig;
4279
4313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -62,15 +62,15 @@
62
62
  },
63
63
  "devDependencies": {
64
64
  "@biomejs/biome": "1.9.4",
65
- "@rollup/plugin-typescript": "12.1.1",
66
- "@types/node": "22.10.1",
67
- "@types/react": "18.3.12",
65
+ "@rollup/plugin-typescript": "12.1.2",
66
+ "@types/node": "22.10.2",
67
+ "@types/react": "19.0.2",
68
68
  "ajv": "8.17.1",
69
- "cspell": "8.16.1",
70
- "magic-string": "0.30.14",
71
- "monocart-coverage-reports": "2.11.3",
69
+ "cspell": "8.17.1",
70
+ "magic-string": "0.30.17",
71
+ "monocart-coverage-reports": "2.11.5",
72
72
  "pretty-ansi": "3.0.0",
73
- "rollup": "4.28.0",
73
+ "rollup": "4.29.1",
74
74
  "rollup-plugin-dts": "6.1.1",
75
75
  "tslib": "2.8.1",
76
76
  "typescript": "5.7.2"