tstyche 3.0.0-rc.0 → 3.0.0-rc.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/index.d.cts CHANGED
@@ -149,7 +149,7 @@ interface Matchers {
149
149
  /**
150
150
  * Checks if the source type raises an error.
151
151
  */
152
- toRaiseError: (...target: Array<string | number>) => void;
152
+ toRaiseError: (...target: Array<string | number | RegExp>) => void;
153
153
  }
154
154
  interface Matchers {
155
155
  /**
package/build/index.d.ts CHANGED
@@ -149,7 +149,7 @@ interface Matchers {
149
149
  /**
150
150
  * Checks if the source type raises an error.
151
151
  */
152
- toRaiseError: (...target: Array<string | number>) => void;
152
+ toRaiseError: (...target: Array<string | number | RegExp>) => void;
153
153
  }
154
154
  interface Matchers {
155
155
  /**
@@ -558,6 +558,7 @@ interface MatchResult {
558
558
  }
559
559
  type Relation = Map<string, unknown>;
560
560
  interface TypeChecker extends ts.TypeChecker {
561
+ isApplicableIndexType: (source: ts.Type, target: ts.Type) => boolean;
561
562
  isTypeRelatedTo: (source: ts.Type, target: ts.Type, relation: Relation) => boolean;
562
563
  relation: {
563
564
  assignable: Relation;
package/build/tstyche.js CHANGED
@@ -701,22 +701,23 @@ class Store {
701
701
  static async #loadModule(modulePath) {
702
702
  const exports = {};
703
703
  const module = { exports };
704
- const candidatePaths = [Path.join(Path.dirname(modulePath), "tsserverlibrary.js"), modulePath];
705
- for (const candidatePath of candidatePaths) {
706
- const sourceText = await fs.readFile(candidatePath, { encoding: "utf8" });
707
- if (!sourceText.includes("isTypeRelatedTo")) {
708
- continue;
709
- }
710
- const toExpose = [
711
- "getTypeOfSymbol",
712
- "isTypeRelatedTo",
713
- "relation: { assignable: assignableRelation, identity: identityRelation, subtype: strictSubtypeRelation }",
714
- ];
715
- const modifiedSourceText = sourceText.replace("return checker;", `return { ...checker, ${toExpose.join(", ")} };`);
716
- const compiledWrapper = vm.compileFunction(modifiedSourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: candidatePath });
717
- compiledWrapper(exports, createRequire(candidatePath), module, candidatePath, Path.dirname(candidatePath));
718
- break;
719
- }
704
+ const packageConfigText = await fs.readFile(Path.resolve(modulePath, "../../package.json"), { encoding: "utf8" });
705
+ const { version: packageVersion } = JSON.parse(packageConfigText);
706
+ if (!Version.isSatisfiedWith(packageVersion, "5.3")) {
707
+ modulePath = Path.resolve(modulePath, "../tsserverlibrary.js");
708
+ }
709
+ const sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
710
+ const toExpose = [];
711
+ if (Version.isSatisfiedWith(packageVersion, "4.4")) {
712
+ toExpose.push("isApplicableIndexType");
713
+ }
714
+ if (!Version.isSatisfiedWith(packageVersion, "4.6")) {
715
+ toExpose.push("getTypeOfSymbol");
716
+ }
717
+ toExpose.push("isTypeRelatedTo", "relation: { assignable: assignableRelation, identity: identityRelation, subtype: strictSubtypeRelation }");
718
+ const modifiedSourceText = sourceText.replace("return checker;", `return { ...checker, ${toExpose.join(", ")} };`);
719
+ const compiledWrapper = vm.compileFunction(modifiedSourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
720
+ compiledWrapper(exports, createRequire(modulePath), module, modulePath, Path.dirname(modulePath));
720
721
  return module.exports;
721
722
  }
722
723
  static #onDiagnostics(diagnostic) {
@@ -961,19 +962,29 @@ class Options {
961
962
  }
962
963
  return definitionMap;
963
964
  }
965
+ static #getCanonicalOptionName(optionName) {
966
+ return optionName.startsWith("--") ? optionName.slice(2) : optionName;
967
+ }
968
+ static #isBuiltinReporter(optionValue) {
969
+ return ["list", "summary"].includes(optionValue);
970
+ }
971
+ static #isLookupStrategy(optionValue) {
972
+ return ["findup", "ignore"].includes(optionValue);
973
+ }
964
974
  static resolve(optionName, optionValue, rootPath = ".") {
965
- switch (optionName.startsWith("--") ? optionName.slice(2) : optionName) {
975
+ const canonicalOptionName = Options.#getCanonicalOptionName(optionName);
976
+ switch (canonicalOptionName) {
966
977
  case "config":
967
978
  case "rootPath":
968
979
  case "tsconfig":
969
- if (optionName.endsWith("tsconfig") && ["findup", "ignore"].includes(optionValue)) {
980
+ if (canonicalOptionName === "tsconfig" && Options.#isLookupStrategy(optionValue)) {
970
981
  break;
971
982
  }
972
983
  optionValue = Path.resolve(rootPath, optionValue);
973
984
  break;
974
985
  case "plugins":
975
986
  case "reporters":
976
- if (optionName.endsWith("reporters") && ["list", "summary"].includes(optionValue)) {
987
+ if (canonicalOptionName === "reporters" && Options.#isBuiltinReporter(optionValue)) {
977
988
  break;
978
989
  }
979
990
  try {
@@ -991,11 +1002,12 @@ class Options {
991
1002
  return optionValue;
992
1003
  }
993
1004
  static async validate(optionName, optionValue, optionBrand, onDiagnostics, origin) {
994
- switch (optionName.startsWith("--") ? optionName.slice(2) : optionName) {
1005
+ const canonicalOptionName = Options.#getCanonicalOptionName(optionName);
1006
+ switch (canonicalOptionName) {
995
1007
  case "config":
996
1008
  case "rootPath":
997
1009
  case "tsconfig":
998
- if (optionName.endsWith("tsconfig") && ["findup", "ignore"].includes(optionValue)) {
1010
+ if (canonicalOptionName === "tsconfig" && Options.#isLookupStrategy(optionValue)) {
999
1011
  break;
1000
1012
  }
1001
1013
  if (existsSync(optionValue)) {
@@ -1005,7 +1017,7 @@ class Options {
1005
1017
  break;
1006
1018
  case "plugins":
1007
1019
  case "reporters":
1008
- if (optionName.endsWith("reporters") && ["list", "summary"].includes(optionValue)) {
1020
+ if (canonicalOptionName === "reporters" && Options.#isBuiltinReporter(optionValue)) {
1009
1021
  break;
1010
1022
  }
1011
1023
  if (optionValue.startsWith("file:") && existsSync(new URL(optionValue))) {
@@ -3271,6 +3283,28 @@ class MatchWorker {
3271
3283
  this.#typeChecker = typeChecker;
3272
3284
  this.assertion = assertion;
3273
3285
  }
3286
+ checkHasApplicableIndexType(sourceNode, targetNode) {
3287
+ const sourceType = this.getType(sourceNode);
3288
+ const targetType = this.getType(targetNode);
3289
+ if (Version.isSatisfiedWith(this.#compiler.version, "4.4")) {
3290
+ return this.#typeChecker
3291
+ .getIndexInfosOfType(sourceType)
3292
+ .some(({ keyType }) => this.#typeChecker.isApplicableIndexType(targetType, keyType));
3293
+ }
3294
+ if (targetType.flags & this.#compiler.TypeFlags.StringLiteral) {
3295
+ return sourceType.getStringIndexType() != null;
3296
+ }
3297
+ if (targetType.flags & this.#compiler.TypeFlags.NumberLiteral) {
3298
+ return (sourceType.getStringIndexType() ?? sourceType.getNumberIndexType()) != null;
3299
+ }
3300
+ return false;
3301
+ }
3302
+ checkHasProperty(sourceNode, propertyNameText) {
3303
+ const sourceType = this.getType(sourceNode);
3304
+ return sourceType
3305
+ .getProperties()
3306
+ .some((property) => this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText);
3307
+ }
3274
3308
  checkIsAssignableTo(sourceNode, targetNode) {
3275
3309
  const relation = this.#typeChecker.relation.assignable;
3276
3310
  return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
@@ -3281,7 +3315,9 @@ class MatchWorker {
3281
3315
  }
3282
3316
  checkIsIdenticalTo(sourceNode, targetNode) {
3283
3317
  const relation = this.#typeChecker.relation.identity;
3284
- return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
3318
+ return (this.#checkIsRelatedTo(sourceNode, targetNode, relation) &&
3319
+ this.checkIsAssignableTo(sourceNode, targetNode) &&
3320
+ this.checkIsAssignableWith(sourceNode, targetNode));
3285
3321
  }
3286
3322
  checkIsSubtype(sourceNode, targetNode) {
3287
3323
  const relation = this.#typeChecker.relation.subtype;
@@ -3643,7 +3679,7 @@ class ToHaveProperty {
3643
3679
  diagnostics.push(Diagnostic.error(text, origin));
3644
3680
  }
3645
3681
  const targetType = matchWorker.getType(targetNode);
3646
- let propertyNameText;
3682
+ let propertyNameText = "";
3647
3683
  if (matchWorker.isStringOrNumberLiteralType(targetType)) {
3648
3684
  propertyNameText = targetType.value.toString();
3649
3685
  }
@@ -3660,9 +3696,8 @@ class ToHaveProperty {
3660
3696
  onDiagnostics(diagnostics);
3661
3697
  return;
3662
3698
  }
3663
- const isMatch = sourceType.getProperties().some((property) => {
3664
- return this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText;
3665
- });
3699
+ const isMatch = matchWorker.checkHasProperty(sourceNode, propertyNameText) ||
3700
+ matchWorker.checkHasApplicableIndexType(sourceNode, targetNode);
3666
3701
  return {
3667
3702
  explain: () => this.#explain(matchWorker, sourceNode, targetNode),
3668
3703
  isMatch,
@@ -3722,8 +3757,10 @@ class ToRaiseError {
3722
3757
  match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
3723
3758
  const diagnostics = [];
3724
3759
  for (const targetNode of targetNodes) {
3725
- if (!(this.#compiler.isStringLiteralLike(targetNode) || this.#compiler.isNumericLiteral(targetNode))) {
3726
- const expectedText = "a string or number literal";
3760
+ if (!(this.#compiler.isStringLiteralLike(targetNode) ||
3761
+ this.#compiler.isNumericLiteral(targetNode) ||
3762
+ this.#compiler.isRegularExpressionLiteral(targetNode))) {
3763
+ const expectedText = "a string, number or regular expression literal";
3727
3764
  const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
3728
3765
  const origin = DiagnosticOrigin.fromNode(targetNode);
3729
3766
  diagnostics.push(Diagnostic.error(text, origin));
@@ -3748,6 +3785,10 @@ class ToRaiseError {
3748
3785
  };
3749
3786
  }
3750
3787
  #matchExpectedError(diagnostic, targetNode) {
3788
+ if (this.#compiler.isRegularExpressionLiteral(targetNode)) {
3789
+ const targetRegex = new RegExp(...targetNode.text.slice(1).split("/"));
3790
+ return targetRegex.test(this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0));
3791
+ }
3751
3792
  if (this.#compiler.isStringLiteralLike(targetNode)) {
3752
3793
  return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
3753
3794
  }
@@ -4096,7 +4137,7 @@ class TaskRunner {
4096
4137
  class Runner {
4097
4138
  #eventEmitter = new EventEmitter();
4098
4139
  #resolvedConfig;
4099
- static version = "3.0.0-rc.0";
4140
+ static version = "3.0.0-rc.2";
4100
4141
  constructor(resolvedConfig) {
4101
4142
  this.#resolvedConfig = resolvedConfig;
4102
4143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "3.0.0-rc.0",
3
+ "version": "3.0.0-rc.2",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -63,16 +63,16 @@
63
63
  "devDependencies": {
64
64
  "@biomejs/biome": "1.9.4",
65
65
  "@rollup/plugin-typescript": "12.1.1",
66
- "@types/node": "22.8.4",
66
+ "@types/node": "22.8.6",
67
67
  "@types/react": "18.3.12",
68
68
  "ajv": "8.17.1",
69
- "cspell": "8.15.4",
69
+ "cspell": "8.15.5",
70
70
  "magic-string": "0.30.12",
71
71
  "monocart-coverage-reports": "2.11.1",
72
72
  "pretty-ansi": "2.0.0",
73
73
  "rollup": "4.24.3",
74
74
  "rollup-plugin-dts": "6.1.1",
75
- "tslib": "2.8.0",
75
+ "tslib": "2.8.1",
76
76
  "typescript": "5.6.3"
77
77
  },
78
78
  "peerDependencies": {