tstyche 4.0.0-beta.3 → 4.0.0-beta.5

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
@@ -63,7 +63,8 @@ Here is the list of all matchers:
63
63
  - `.toBe()`, `.toBeAssignableTo()`, `.toBeAssignableWith()` compare types or types of expression,
64
64
  - `.toAcceptProps()` checks the type of JSX component props,
65
65
  - `.toBeApplicable` ensures that the decorator function can be applied,
66
- - `.toBeCallableWith()` checks whether a function can be called with the given arguments,
66
+ - `.toBeCallableWith()` checks whether a function is callable with the given arguments,
67
+ - `.toBeConstructableWith()` checks whether a class is constructable with the given arguments,
67
68
  - `.toHaveProperty()` looks up keys on an object type,
68
69
  - `.toRaiseError()` captures the message or code of a type error.
69
70
 
package/build/index.d.cts CHANGED
@@ -128,9 +128,13 @@ interface Matchers {
128
128
  (target: unknown): void;
129
129
  };
130
130
  /**
131
- * Checks if the source type can be called with the target arguments.
131
+ * Checks if the source type is callable with the given arguments.
132
132
  */
133
133
  toBeCallableWith: (...target: Array<unknown>) => void;
134
+ /**
135
+ * Checks if the source type is constructable with the given arguments.
136
+ */
137
+ toBeConstructableWith: (...target: Array<unknown>) => void;
134
138
  /**
135
139
  * Checks if a property key exists on the source type.
136
140
  */
package/build/index.d.ts CHANGED
@@ -128,9 +128,13 @@ interface Matchers {
128
128
  (target: unknown): void;
129
129
  };
130
130
  /**
131
- * Checks if the source type can be called with the target arguments.
131
+ * Checks if the source type is callable with the given arguments.
132
132
  */
133
133
  toBeCallableWith: (...target: Array<unknown>) => void;
134
+ /**
135
+ * Checks if the source type is constructable with the given arguments.
136
+ */
137
+ toBeConstructableWith: (...target: Array<unknown>) => void;
134
138
  /**
135
139
  * Checks if a property key exists on the source type.
136
140
  */
@@ -62,10 +62,14 @@ declare class Diagnostic {
62
62
  static error(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
63
63
  extendWith(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
64
64
  static fromDiagnostics(diagnostics: Array<ts.Diagnostic>): Array<Diagnostic>;
65
- static toMessageText(chain: ts.DiagnosticMessageChain): Array<string>;
66
65
  static warning(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
67
66
  }
68
67
 
68
+ declare function diagnosticBelongsToNode(diagnostic: ts.Diagnostic, node: ts.NodeArray<ts.Node> | ts.Node): boolean;
69
+ declare function getDiagnosticMessageText(diagnostic: ts.Diagnostic): string | Array<string>;
70
+ declare function getTextSpanEnd(span: ts.TextSpan): number;
71
+ declare function isDiagnosticWithLocation(diagnostic: ts.Diagnostic): diagnostic is ts.DiagnosticWithLocation;
72
+
69
73
  type DiagnosticsHandler<T extends Diagnostic | Array<Diagnostic> = Diagnostic> = (this: void, diagnostics: T) => void;
70
74
 
71
75
  declare enum TestTreeNodeBrand {
@@ -604,6 +608,7 @@ declare class ExpectService {
604
608
  private toBeAssignableTo;
605
609
  private toBeAssignableWith;
606
610
  private toBeCallableWith;
611
+ private toBeConstructableWith;
607
612
  private toHaveProperty;
608
613
  private toRaiseError;
609
614
  constructor(compiler: typeof ts, typeChecker: TypeChecker, resolvedConfig: ResolvedConfig);
@@ -868,5 +873,5 @@ declare class WatchService {
868
873
  watch(cancellationToken: CancellationToken): AsyncIterable<Array<Task>>;
869
874
  }
870
875
 
871
- export { AssertionNode, 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, ScribblerJsx, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeHandler, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
876
+ export { AssertionNode, 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, ScribblerJsx, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeHandler, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
872
877
  export type { CodeFrameOptions, CommandLineOptions, ConfigFileOptions, DiagnosticsHandler, EnvironmentOptions, Event, EventHandler, FileWatchHandler, InputHandler, ItemDefinition, MatchResult, OptionDefinition, Plugin, Reporter, ReporterEvent, ResolvedConfig, ScribblerOptions, SelectHookContext, TargetResultStatus, TaskResultStatus, TypeChecker, WatchHandler, WatcherOptions };
package/build/tstyche.js CHANGED
@@ -104,6 +104,30 @@ class DiagnosticOrigin {
104
104
  }
105
105
  }
106
106
 
107
+ function diagnosticBelongsToNode(diagnostic, node) {
108
+ return diagnostic.start != null && diagnostic.start >= node.pos && diagnostic.start <= node.end;
109
+ }
110
+ function diagnosticMessageChainToText(chain) {
111
+ const result = [chain.messageText];
112
+ if (chain.next != null) {
113
+ for (const nextChain of chain.next) {
114
+ result.push(...diagnosticMessageChainToText(nextChain));
115
+ }
116
+ }
117
+ return result;
118
+ }
119
+ function getDiagnosticMessageText(diagnostic) {
120
+ return typeof diagnostic.messageText === "string"
121
+ ? diagnostic.messageText
122
+ : diagnosticMessageChainToText(diagnostic.messageText);
123
+ }
124
+ function getTextSpanEnd(span) {
125
+ return span.start + span.length;
126
+ }
127
+ function isDiagnosticWithLocation(diagnostic) {
128
+ return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
129
+ }
130
+
107
131
  class Diagnostic {
108
132
  category;
109
133
  code;
@@ -141,21 +165,10 @@ class Diagnostic {
141
165
  if (diagnostic.relatedInformation != null) {
142
166
  related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
143
167
  }
144
- const text = typeof diagnostic.messageText === "string"
145
- ? diagnostic.messageText
146
- : Diagnostic.toMessageText(diagnostic.messageText);
168
+ const text = getDiagnosticMessageText(diagnostic);
147
169
  return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
148
170
  });
149
171
  }
150
- static toMessageText(chain) {
151
- const result = [chain.messageText];
152
- if (chain.next != null) {
153
- for (const nextChain of chain.next) {
154
- result.push(...Diagnostic.toMessageText(nextChain));
155
- }
156
- }
157
- return result;
158
- }
159
172
  static warning(text, origin) {
160
173
  return new Diagnostic(text, DiagnosticCategory.Warning, origin);
161
174
  }
@@ -1927,13 +1940,9 @@ class TestTreeNode {
1927
1940
  if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
1928
1941
  this.name = node.arguments[0].text;
1929
1942
  }
1930
- if (node.arguments[1] != null &&
1931
- compiler.isFunctionLike(node.arguments[1]) &&
1932
- compiler.isBlock(node.arguments[1].body)) {
1933
- const blockStart = node.arguments[1].body.getStart();
1934
- const blockEnd = node.arguments[1].body.getEnd();
1943
+ if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
1935
1944
  for (const diagnostic of parent.diagnostics) {
1936
- if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
1945
+ if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
1937
1946
  this.diagnostics.add(diagnostic);
1938
1947
  parent.diagnostics.delete(diagnostic);
1939
1948
  }
@@ -1990,7 +1999,7 @@ class AssertionNode extends TestTreeNode {
1990
1999
  this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
1991
2000
  }
1992
2001
  for (const diagnostic of parent.diagnostics) {
1993
- if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
2002
+ if (diagnosticBelongsToNode(diagnostic, this.source)) {
1994
2003
  this.diagnostics.add(diagnostic);
1995
2004
  parent.diagnostics.delete(diagnostic);
1996
2005
  }
@@ -2029,7 +2038,9 @@ class AbilityLayer {
2029
2038
  #addRanges(node, ranges) {
2030
2039
  this.#nodes.push(node);
2031
2040
  for (const range of ranges) {
2032
- const rangeText = this.#getErasedRangeText(range);
2041
+ const rangeText = range.replacement != null
2042
+ ? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
2043
+ : this.#getErasedRangeText(range);
2033
2044
  this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
2034
2045
  }
2035
2046
  }
@@ -2038,12 +2049,9 @@ class AbilityLayer {
2038
2049
  this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
2039
2050
  const languageService = this.#projectService.getLanguageService(this.#filePath);
2040
2051
  const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
2041
- const nodes = this.#nodes.reverse();
2042
- for (const diagnostic of diagnostics) {
2043
- for (const node of nodes) {
2044
- if (diagnostic.start != null &&
2045
- diagnostic.start >= node.matcherNode.pos &&
2046
- diagnostic.start <= node.matcherNode.end) {
2052
+ for (const node of this.#nodes.reverse()) {
2053
+ for (const diagnostic of diagnostics) {
2054
+ if (diagnosticBelongsToNode(diagnostic, node.matcherNode)) {
2047
2055
  if (!node.abilityDiagnostics) {
2048
2056
  node.abilityDiagnostics = new Set();
2049
2057
  }
@@ -2058,29 +2066,29 @@ class AbilityLayer {
2058
2066
  this.#text = "";
2059
2067
  }
2060
2068
  handleNode(assertionNode) {
2069
+ const expectStart = assertionNode.node.getStart();
2070
+ const expectExpressionEnd = assertionNode.node.expression.getEnd();
2071
+ const expectEnd = assertionNode.node.getEnd();
2072
+ const matcherNameEnd = assertionNode.matcherNameNode.getEnd();
2061
2073
  switch (assertionNode.matcherNameNode.name.text) {
2062
- case "toBeApplicable": {
2063
- const expectStart = assertionNode.node.pos;
2064
- const expectExpressionEnd = assertionNode.node.expression.end;
2065
- const expectEnd = assertionNode.node.end;
2066
- const matcherNameEnd = assertionNode.matcherNameNode.end;
2074
+ case "toBeApplicable":
2067
2075
  this.#addRanges(assertionNode, [
2068
- { end: expectExpressionEnd + 1, start: expectStart },
2069
- { end: matcherNameEnd, start: expectEnd - 1 },
2076
+ { end: expectExpressionEnd, start: expectStart },
2077
+ { end: matcherNameEnd, start: expectEnd },
2070
2078
  ]);
2071
2079
  break;
2072
- }
2073
- case "toBeCallableWith": {
2074
- const expectStart = assertionNode.node.pos;
2075
- const expectExpressionEnd = assertionNode.node.expression.end;
2076
- const expectEnd = assertionNode.node.end;
2077
- const matcherNameEnd = assertionNode.matcherNameNode.end;
2080
+ case "toBeCallableWith":
2078
2081
  this.#addRanges(assertionNode, [
2079
- { end: expectExpressionEnd + 1, start: expectStart },
2080
- { end: matcherNameEnd, start: expectEnd - 1 },
2082
+ { end: expectExpressionEnd, start: expectStart, replacement: ";" },
2083
+ { end: matcherNameEnd, start: expectEnd },
2084
+ ]);
2085
+ break;
2086
+ case "toBeConstructableWith":
2087
+ this.#addRanges(assertionNode, [
2088
+ { end: expectExpressionEnd, start: expectStart, replacement: "; new" },
2089
+ { end: matcherNameEnd, start: expectEnd },
2081
2090
  ]);
2082
2091
  break;
2083
- }
2084
2092
  }
2085
2093
  }
2086
2094
  open(sourceFile) {
@@ -3448,11 +3456,17 @@ class ExpectDiagnosticText {
3448
3456
  static argumentMustBeProvided(argumentNameText) {
3449
3457
  return `An argument for '${argumentNameText}' must be provided.`;
3450
3458
  }
3451
- static canBeCalled(isTypeNode, targetText) {
3452
- return `${isTypeNode ? "Type" : "Expression"} can be called ${targetText}.`;
3459
+ static isCallable(isTypeNode, targetText) {
3460
+ return `${isTypeNode ? "Type" : "Expression"} is callable ${targetText}.`;
3453
3461
  }
3454
- static cannotBeCalled(isTypeNode, targetText) {
3455
- return `${isTypeNode ? "Type" : "Expression"} cannot be called ${targetText}.`;
3462
+ static isNotCallable(isTypeNode, targetText) {
3463
+ return `${isTypeNode ? "Type" : "Expression"} is not callable ${targetText}.`;
3464
+ }
3465
+ static isConstructable(isTypeNode, targetText) {
3466
+ return `${isTypeNode ? "Type" : "Expression"} is constructable ${targetText}.`;
3467
+ }
3468
+ static isNotConstructable(isTypeNode, targetText) {
3469
+ return `${isTypeNode ? "Type" : "Expression"} is not constructable ${targetText}.`;
3456
3470
  }
3457
3471
  static acceptsProps(isTypeNode) {
3458
3472
  return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
@@ -3472,6 +3486,9 @@ class ExpectDiagnosticText {
3472
3486
  static hasProperty(typeText, propertyNameText) {
3473
3487
  return `Type '${typeText}' has property '${propertyNameText}'.`;
3474
3488
  }
3489
+ static didYouMeanToUse(suggestionText) {
3490
+ return `Did you mean to use ${suggestionText}?`;
3491
+ }
3475
3492
  static matcherIsNotSupported(matcherNameText) {
3476
3493
  return `The '.${matcherNameText}()' matcher is not supported.`;
3477
3494
  }
@@ -3543,18 +3560,18 @@ class MatchWorker {
3543
3560
  assertion;
3544
3561
  #compiler;
3545
3562
  #signatureCache = new Map();
3546
- #typeChecker;
3563
+ typeChecker;
3547
3564
  constructor(compiler, typeChecker, assertion) {
3548
3565
  this.#compiler = compiler;
3549
- this.#typeChecker = typeChecker;
3566
+ this.typeChecker = typeChecker;
3550
3567
  this.assertion = assertion;
3551
3568
  }
3552
3569
  checkHasApplicableIndexType(sourceNode, targetNode) {
3553
3570
  const sourceType = this.getType(sourceNode);
3554
3571
  const targetType = this.getType(targetNode);
3555
- return this.#typeChecker
3572
+ return this.typeChecker
3556
3573
  .getIndexInfosOfType(sourceType)
3557
- .some(({ keyType }) => this.#typeChecker.isApplicableIndexType(targetType, keyType));
3574
+ .some(({ keyType }) => this.typeChecker.isApplicableIndexType(targetType, keyType));
3558
3575
  }
3559
3576
  checkHasProperty(sourceNode, propertyNameText) {
3560
3577
  const sourceType = this.getType(sourceNode);
@@ -3563,31 +3580,31 @@ class MatchWorker {
3563
3580
  .some((property) => this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText);
3564
3581
  }
3565
3582
  checkIsAssignableTo(sourceNode, targetNode) {
3566
- const relation = this.#typeChecker.relation.assignable;
3583
+ const relation = this.typeChecker.relation.assignable;
3567
3584
  return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
3568
3585
  }
3569
3586
  checkIsAssignableWith(sourceNode, targetNode) {
3570
- const relation = this.#typeChecker.relation.assignable;
3587
+ const relation = this.typeChecker.relation.assignable;
3571
3588
  return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
3572
3589
  }
3573
3590
  checkIsIdenticalTo(sourceNode, targetNode) {
3574
- const relation = this.#typeChecker.relation.identity;
3591
+ const relation = this.typeChecker.relation.identity;
3575
3592
  return (this.#checkIsRelatedTo(sourceNode, targetNode, relation) &&
3576
3593
  this.checkIsAssignableTo(sourceNode, targetNode) &&
3577
3594
  this.checkIsAssignableWith(sourceNode, targetNode));
3578
3595
  }
3579
3596
  #checkIsRelatedTo(sourceNode, targetNode, relation) {
3580
- const sourceType = relation === this.#typeChecker.relation.identity
3597
+ const sourceType = relation === this.typeChecker.relation.identity
3581
3598
  ? this.#simplifyType(this.getType(sourceNode))
3582
3599
  : this.getType(sourceNode);
3583
- const targetType = relation === this.#typeChecker.relation.identity
3600
+ const targetType = relation === this.typeChecker.relation.identity
3584
3601
  ? this.#simplifyType(this.getType(targetNode))
3585
3602
  : this.getType(targetNode);
3586
- return this.#typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
3603
+ return this.typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
3587
3604
  }
3588
3605
  extendsObjectType(type) {
3589
3606
  const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
3590
- return this.#typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
3607
+ return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
3591
3608
  }
3592
3609
  getParameterType(signature, index) {
3593
3610
  const parameter = signature.getDeclaration().parameters[index];
@@ -3608,23 +3625,10 @@ class MatchWorker {
3608
3625
  return signatures;
3609
3626
  }
3610
3627
  getTypeText(node) {
3611
- const type = this.getType(node);
3612
- return this.#typeChecker.typeToString(type);
3628
+ return this.typeChecker.typeToString(this.getType(node));
3613
3629
  }
3614
3630
  getType(node) {
3615
- return this.#typeChecker.getTypeAtLocation(node);
3616
- }
3617
- isStringOrNumberLiteralType(type) {
3618
- return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
3619
- }
3620
- isObjectType(type) {
3621
- return !!(type.flags & this.#compiler.TypeFlags.Object);
3622
- }
3623
- isUnionType(type) {
3624
- return !!(type.flags & this.#compiler.TypeFlags.Union);
3625
- }
3626
- isUniqueSymbolType(type) {
3627
- return !!(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
3631
+ return this.typeChecker.getTypeAtLocation(node);
3628
3632
  }
3629
3633
  resolveDiagnosticOrigin(symbol, enclosingNode) {
3630
3634
  if (symbol.valueDeclaration != null &&
@@ -3640,7 +3644,7 @@ class MatchWorker {
3640
3644
  #simplifyType(type) {
3641
3645
  if (type.isUnionOrIntersection()) {
3642
3646
  const candidateType = this.#simplifyType(type.types[0]);
3643
- if (type.types.every((type) => this.#typeChecker.isTypeRelatedTo(this.#simplifyType(type), candidateType, this.#typeChecker.relation.identity))) {
3647
+ if (type.types.every((type) => this.typeChecker.isTypeRelatedTo(this.#simplifyType(type), candidateType, this.typeChecker.relation.identity))) {
3644
3648
  return candidateType;
3645
3649
  }
3646
3650
  }
@@ -3648,6 +3652,16 @@ class MatchWorker {
3648
3652
  }
3649
3653
  }
3650
3654
 
3655
+ function isStringOrNumberLiteralType(compiler, type) {
3656
+ return !!(type.flags & compiler.TypeFlags.StringOrNumberLiteral);
3657
+ }
3658
+ function isUnionType(compiler, type) {
3659
+ return !!(type.flags & compiler.TypeFlags.Union);
3660
+ }
3661
+ function isUniqueSymbolType(compiler, type) {
3662
+ return !!(type.flags & compiler.TypeFlags.UniqueESSymbol);
3663
+ }
3664
+
3651
3665
  class ToAcceptProps {
3652
3666
  #compiler;
3653
3667
  #typeChecker;
@@ -3681,7 +3695,7 @@ class ToAcceptProps {
3681
3695
  #isOptionalProperty(symbol) {
3682
3696
  return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
3683
3697
  }
3684
- #checkProperties(matchWorker, sourceType, targetType) {
3698
+ #checkProperties(sourceType, targetType) {
3685
3699
  const check = (sourceType, targetType) => {
3686
3700
  for (const targetProperty of targetType.getProperties()) {
3687
3701
  const targetPropertyName = targetProperty.getName();
@@ -3709,7 +3723,7 @@ class ToAcceptProps {
3709
3723
  }
3710
3724
  return true;
3711
3725
  };
3712
- if (sourceType != null && matchWorker.isUnionType(sourceType)) {
3726
+ if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
3713
3727
  return sourceType.types.some((sourceType) => check(sourceType, targetType));
3714
3728
  }
3715
3729
  return check(sourceType, targetType);
@@ -3777,7 +3791,7 @@ class ToAcceptProps {
3777
3791
  }
3778
3792
  return { diagnostics, isMatch: false };
3779
3793
  };
3780
- if (sourceType != null && matchWorker.isUnionType(sourceType)) {
3794
+ if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
3781
3795
  let accumulator = [];
3782
3796
  const isMatch = sourceType.types.some((sourceType) => {
3783
3797
  const text = matchWorker.assertion.isNot
@@ -3808,7 +3822,7 @@ class ToAcceptProps {
3808
3822
  diagnostics.push(Diagnostic.error(text, origin));
3809
3823
  }
3810
3824
  const targetType = matchWorker.getType(targetNode);
3811
- if (!matchWorker.isObjectType(targetType)) {
3825
+ if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
3812
3826
  const expectedText = "of an object type";
3813
3827
  const text = this.#compiler.isTypeNode(targetNode)
3814
3828
  ? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
@@ -3822,7 +3836,7 @@ class ToAcceptProps {
3822
3836
  }
3823
3837
  const isMatch = signatures.some((signature) => {
3824
3838
  const sourceType = matchWorker.getParameterType(signature, 0);
3825
- return this.#checkProperties(matchWorker, sourceType, targetType);
3839
+ return this.#checkProperties(sourceType, targetType);
3826
3840
  });
3827
3841
  return {
3828
3842
  explain: () => this.#explain(matchWorker, sourceNode, targetNode),
@@ -3890,12 +3904,7 @@ class ToBeApplicable {
3890
3904
  const diagnostics = [];
3891
3905
  if (matchWorker.assertion.abilityDiagnostics) {
3892
3906
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3893
- const text = [
3894
- ExpectDiagnosticText.cannotBeApplied(targetText),
3895
- typeof diagnostic.messageText === "string"
3896
- ? diagnostic.messageText
3897
- : Diagnostic.toMessageText(diagnostic.messageText),
3898
- ];
3907
+ const text = [ExpectDiagnosticText.cannotBeApplied(targetText), getDiagnosticMessageText(diagnostic)];
3899
3908
  const origin = DiagnosticOrigin.fromNode(sourceNode);
3900
3909
  diagnostics.push(Diagnostic.error(text.flat(), origin));
3901
3910
  }
@@ -3951,8 +3960,83 @@ class ToBeCallableWith {
3951
3960
  constructor(compiler) {
3952
3961
  this.#compiler = compiler;
3953
3962
  }
3954
- isDiagnosticWithLocation(diagnostic) {
3955
- return diagnostic.start != null;
3963
+ #resolveTargetText(nodes) {
3964
+ if (nodes.length === 0) {
3965
+ return "without arguments";
3966
+ }
3967
+ if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
3968
+ return "with the given arguments";
3969
+ }
3970
+ return `with the given argument${nodes.length === 1 ? "" : "s"}`;
3971
+ }
3972
+ #explain(matchWorker, sourceNode, targetNodes) {
3973
+ const isTypeNode = this.#compiler.isTypeNode(sourceNode);
3974
+ const targetText = this.#resolveTargetText(targetNodes);
3975
+ const diagnostics = [];
3976
+ if (matchWorker.assertion.abilityDiagnostics) {
3977
+ for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3978
+ const text = [ExpectDiagnosticText.isNotCallable(isTypeNode, targetText), getDiagnosticMessageText(diagnostic)];
3979
+ let origin;
3980
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
3981
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
3982
+ }
3983
+ else {
3984
+ origin =
3985
+ targetNodes.length > 0
3986
+ ? DiagnosticOrigin.fromNodes(targetNodes)
3987
+ : DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3988
+ }
3989
+ let related;
3990
+ if (diagnostic.relatedInformation != null) {
3991
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
3992
+ }
3993
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
3994
+ }
3995
+ }
3996
+ else {
3997
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3998
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isTypeNode, targetText), origin));
3999
+ }
4000
+ return diagnostics;
4001
+ }
4002
+ match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
4003
+ let type;
4004
+ if (this.#compiler.isCallExpression(sourceNode)) {
4005
+ type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4006
+ }
4007
+ if (this.#compiler.isArrowFunction(sourceNode) ||
4008
+ this.#compiler.isFunctionDeclaration(sourceNode) ||
4009
+ this.#compiler.isFunctionExpression(sourceNode) ||
4010
+ this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
4011
+ this.#compiler.isIdentifier(sourceNode)) {
4012
+ type = matchWorker.getType(sourceNode);
4013
+ }
4014
+ if (!type || type.getCallSignatures().length === 0) {
4015
+ const text = [];
4016
+ if (this.#compiler.isTypeNode(sourceNode)) {
4017
+ text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
4018
+ }
4019
+ else {
4020
+ text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
4021
+ }
4022
+ if (type != null && type.getConstructSignatures().length > 0) {
4023
+ text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
4024
+ }
4025
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
4026
+ onDiagnostics([Diagnostic.error(text, origin)]);
4027
+ return;
4028
+ }
4029
+ return {
4030
+ explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4031
+ isMatch: !matchWorker.assertion.abilityDiagnostics,
4032
+ };
4033
+ }
4034
+ }
4035
+
4036
+ class ToBeConstructableWith {
4037
+ #compiler;
4038
+ constructor(compiler) {
4039
+ this.#compiler = compiler;
3956
4040
  }
3957
4041
  #resolveTargetText(nodes) {
3958
4042
  if (nodes.length === 0) {
@@ -3970,16 +4054,12 @@ class ToBeCallableWith {
3970
4054
  if (matchWorker.assertion.abilityDiagnostics) {
3971
4055
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3972
4056
  const text = [
3973
- ExpectDiagnosticText.cannotBeCalled(isTypeNode, targetText),
3974
- typeof diagnostic.messageText === "string"
3975
- ? diagnostic.messageText
3976
- : Diagnostic.toMessageText(diagnostic.messageText),
4057
+ ExpectDiagnosticText.isNotConstructable(isTypeNode, targetText),
4058
+ getDiagnosticMessageText(diagnostic),
3977
4059
  ];
3978
4060
  let origin;
3979
- if (this.isDiagnosticWithLocation(diagnostic) &&
3980
- diagnostic.start >= targetNodes.pos &&
3981
- diagnostic.start <= targetNodes.end) {
3982
- origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, sourceNode.getSourceFile());
4061
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4062
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
3983
4063
  }
3984
4064
  else {
3985
4065
  origin =
@@ -3996,11 +4076,34 @@ class ToBeCallableWith {
3996
4076
  }
3997
4077
  else {
3998
4078
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3999
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeCalled(isTypeNode, targetText), origin));
4079
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isTypeNode, targetText), origin));
4000
4080
  }
4001
4081
  return diagnostics;
4002
4082
  }
4003
- match(matchWorker, sourceNode, targetNodes, _onDiagnostics) {
4083
+ match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
4084
+ let type;
4085
+ if (this.#compiler.isCallExpression(sourceNode)) {
4086
+ type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4087
+ }
4088
+ if (this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
4089
+ this.#compiler.isIdentifier(sourceNode)) {
4090
+ type = matchWorker.getType(sourceNode);
4091
+ }
4092
+ if (!type || type.getConstructSignatures().length === 0) {
4093
+ const text = [];
4094
+ if (this.#compiler.isTypeNode(sourceNode)) {
4095
+ text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
4096
+ }
4097
+ else {
4098
+ text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
4099
+ }
4100
+ if (type != null && type.getCallSignatures().length > 0) {
4101
+ text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeCallableWith()' matcher"));
4102
+ }
4103
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
4104
+ onDiagnostics([Diagnostic.error(text, origin)]);
4105
+ return;
4106
+ }
4004
4107
  return {
4005
4108
  explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4006
4109
  isMatch: !matchWorker.assertion.abilityDiagnostics,
@@ -4017,7 +4120,7 @@ class ToHaveProperty {
4017
4120
  const sourceTypeText = matchWorker.getTypeText(sourceNode);
4018
4121
  const targetType = matchWorker.getType(targetNode);
4019
4122
  let propertyNameText;
4020
- if (matchWorker.isStringOrNumberLiteralType(targetType)) {
4123
+ if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
4021
4124
  propertyNameText = targetType.value.toString();
4022
4125
  }
4023
4126
  else {
@@ -4042,10 +4145,10 @@ class ToHaveProperty {
4042
4145
  }
4043
4146
  const targetType = matchWorker.getType(targetNode);
4044
4147
  let propertyNameText = "";
4045
- if (matchWorker.isStringOrNumberLiteralType(targetType)) {
4148
+ if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
4046
4149
  propertyNameText = targetType.value.toString();
4047
4150
  }
4048
- else if (matchWorker.isUniqueSymbolType(targetType)) {
4151
+ else if (isUniqueSymbolType(this.#compiler, targetType)) {
4049
4152
  propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
4050
4153
  }
4051
4154
  else {
@@ -4139,9 +4242,10 @@ class ToRaiseError {
4139
4242
  if (this.#compiler.isNumericLiteral(targetNode)) {
4140
4243
  return Number.parseInt(targetNode.text, 10) === diagnostic.code;
4141
4244
  }
4142
- const messageText = typeof diagnostic.messageText === "string"
4143
- ? diagnostic.messageText
4144
- : Diagnostic.toMessageText(diagnostic.messageText).join("\n");
4245
+ let messageText = getDiagnosticMessageText(diagnostic);
4246
+ if (Array.isArray(messageText)) {
4247
+ messageText = messageText.join("\n");
4248
+ }
4145
4249
  if (this.#compiler.isRegularExpressionLiteral(targetNode)) {
4146
4250
  const targetRegex = new RegExp(...targetNode.text.slice(1).split("/"));
4147
4251
  return targetRegex.test(messageText);
@@ -4160,6 +4264,7 @@ class ExpectService {
4160
4264
  toBeAssignableTo;
4161
4265
  toBeAssignableWith;
4162
4266
  toBeCallableWith;
4267
+ toBeConstructableWith;
4163
4268
  toHaveProperty;
4164
4269
  toRaiseError;
4165
4270
  constructor(compiler, typeChecker, resolvedConfig) {
@@ -4177,6 +4282,7 @@ class ExpectService {
4177
4282
  this.toBeAssignableTo = new ToBeAssignableTo();
4178
4283
  this.toBeAssignableWith = new ToBeAssignableWith();
4179
4284
  this.toBeCallableWith = new ToBeCallableWith(compiler);
4285
+ this.toBeConstructableWith = new ToBeConstructableWith(compiler);
4180
4286
  this.toHaveProperty = new ToHaveProperty(compiler);
4181
4287
  this.toRaiseError = new ToRaiseError(compiler);
4182
4288
  }
@@ -4204,6 +4310,7 @@ class ExpectService {
4204
4310
  case "toBeApplicable":
4205
4311
  return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
4206
4312
  case "toBeCallableWith":
4313
+ case "toBeConstructableWith":
4207
4314
  case "toRaiseError":
4208
4315
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
4209
4316
  case "toHaveProperty":
@@ -4491,7 +4598,7 @@ class TaskRunner {
4491
4598
  class Runner {
4492
4599
  #eventEmitter = new EventEmitter();
4493
4600
  #resolvedConfig;
4494
- static version = "4.0.0-beta.3";
4601
+ static version = "4.0.0-beta.5";
4495
4602
  constructor(resolvedConfig) {
4496
4603
  this.#resolvedConfig = resolvedConfig;
4497
4604
  }
@@ -4693,4 +4800,4 @@ class Cli {
4693
4800
  }
4694
4801
  }
4695
4802
 
4696
- export { AssertionNode, 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, TestResult, TestTree, TestTreeHandler, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
4803
+ export { AssertionNode, 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, TestResult, TestTree, TestTreeHandler, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "4.0.0-beta.3",
3
+ "version": "4.0.0-beta.5",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -62,14 +62,14 @@
62
62
  "devDependencies": {
63
63
  "@biomejs/biome": "1.9.4",
64
64
  "@rollup/plugin-typescript": "12.1.2",
65
- "@types/node": "22.14.0",
66
- "@types/react": "19.1.0",
65
+ "@types/node": "22.14.1",
66
+ "@types/react": "19.1.2",
67
67
  "ajv": "8.17.1",
68
68
  "cspell": "8.18.1",
69
69
  "magic-string": "0.30.17",
70
- "monocart-coverage-reports": "2.12.3",
70
+ "monocart-coverage-reports": "2.12.4",
71
71
  "pretty-ansi": "3.0.0",
72
- "rollup": "4.39.0",
72
+ "rollup": "4.40.0",
73
73
  "rollup-plugin-dts": "6.2.1",
74
74
  "tslib": "2.8.1",
75
75
  "typescript": "5.8.3"
@@ -82,7 +82,7 @@
82
82
  "optional": true
83
83
  }
84
84
  },
85
- "packageManager": "yarn@4.9.0",
85
+ "packageManager": "yarn@4.9.1",
86
86
  "engines": {
87
87
  "node": ">=20.9"
88
88
  }