tstyche 4.0.0-beta.2 → 4.0.0-beta.4

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,24 @@ 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":
2075
+ case "toBeCallableWith":
2067
2076
  this.#addRanges(assertionNode, [
2068
- { end: expectExpressionEnd + 1, start: expectStart },
2069
- { end: matcherNameEnd, start: expectEnd - 1 },
2077
+ { end: expectExpressionEnd, start: expectStart },
2078
+ { end: matcherNameEnd, start: expectEnd },
2070
2079
  ]);
2071
2080
  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;
2081
+ case "toBeConstructableWith":
2078
2082
  this.#addRanges(assertionNode, [
2079
- { end: expectExpressionEnd + 1, start: expectStart },
2080
- { end: matcherNameEnd, start: expectEnd - 1 },
2083
+ { end: expectExpressionEnd, start: expectStart, replacement: "new" },
2084
+ { end: matcherNameEnd, start: expectEnd },
2081
2085
  ]);
2082
2086
  break;
2083
- }
2084
2087
  }
2085
2088
  }
2086
2089
  open(sourceFile) {
@@ -3448,11 +3451,17 @@ class ExpectDiagnosticText {
3448
3451
  static argumentMustBeProvided(argumentNameText) {
3449
3452
  return `An argument for '${argumentNameText}' must be provided.`;
3450
3453
  }
3451
- static canBeCalled(isTypeNode, targetText) {
3452
- return `${isTypeNode ? "Type" : "Expression"} can be called ${targetText}.`;
3454
+ static isCallable(isTypeNode, targetText) {
3455
+ return `${isTypeNode ? "Type" : "Expression"} is callable ${targetText}.`;
3456
+ }
3457
+ static isNotCallable(isTypeNode, targetText) {
3458
+ return `${isTypeNode ? "Type" : "Expression"} is not callable ${targetText}.`;
3459
+ }
3460
+ static isConstructable(isTypeNode, targetText) {
3461
+ return `${isTypeNode ? "Type" : "Expression"} is constructable ${targetText}.`;
3453
3462
  }
3454
- static cannotBeCalled(isTypeNode, targetText) {
3455
- return `${isTypeNode ? "Type" : "Expression"} cannot be called ${targetText}.`;
3463
+ static isNotConstructable(isTypeNode, targetText) {
3464
+ return `${isTypeNode ? "Type" : "Expression"} is not constructable ${targetText}.`;
3456
3465
  }
3457
3466
  static acceptsProps(isTypeNode) {
3458
3467
  return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
@@ -3472,6 +3481,9 @@ class ExpectDiagnosticText {
3472
3481
  static hasProperty(typeText, propertyNameText) {
3473
3482
  return `Type '${typeText}' has property '${propertyNameText}'.`;
3474
3483
  }
3484
+ static didYouMeanToUse(suggestionText) {
3485
+ return `Did you mean to use ${suggestionText}?`;
3486
+ }
3475
3487
  static matcherIsNotSupported(matcherNameText) {
3476
3488
  return `The '.${matcherNameText}()' matcher is not supported.`;
3477
3489
  }
@@ -3543,18 +3555,18 @@ class MatchWorker {
3543
3555
  assertion;
3544
3556
  #compiler;
3545
3557
  #signatureCache = new Map();
3546
- #typeChecker;
3558
+ typeChecker;
3547
3559
  constructor(compiler, typeChecker, assertion) {
3548
3560
  this.#compiler = compiler;
3549
- this.#typeChecker = typeChecker;
3561
+ this.typeChecker = typeChecker;
3550
3562
  this.assertion = assertion;
3551
3563
  }
3552
3564
  checkHasApplicableIndexType(sourceNode, targetNode) {
3553
3565
  const sourceType = this.getType(sourceNode);
3554
3566
  const targetType = this.getType(targetNode);
3555
- return this.#typeChecker
3567
+ return this.typeChecker
3556
3568
  .getIndexInfosOfType(sourceType)
3557
- .some(({ keyType }) => this.#typeChecker.isApplicableIndexType(targetType, keyType));
3569
+ .some(({ keyType }) => this.typeChecker.isApplicableIndexType(targetType, keyType));
3558
3570
  }
3559
3571
  checkHasProperty(sourceNode, propertyNameText) {
3560
3572
  const sourceType = this.getType(sourceNode);
@@ -3563,31 +3575,31 @@ class MatchWorker {
3563
3575
  .some((property) => this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText);
3564
3576
  }
3565
3577
  checkIsAssignableTo(sourceNode, targetNode) {
3566
- const relation = this.#typeChecker.relation.assignable;
3578
+ const relation = this.typeChecker.relation.assignable;
3567
3579
  return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
3568
3580
  }
3569
3581
  checkIsAssignableWith(sourceNode, targetNode) {
3570
- const relation = this.#typeChecker.relation.assignable;
3582
+ const relation = this.typeChecker.relation.assignable;
3571
3583
  return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
3572
3584
  }
3573
3585
  checkIsIdenticalTo(sourceNode, targetNode) {
3574
- const relation = this.#typeChecker.relation.identity;
3586
+ const relation = this.typeChecker.relation.identity;
3575
3587
  return (this.#checkIsRelatedTo(sourceNode, targetNode, relation) &&
3576
3588
  this.checkIsAssignableTo(sourceNode, targetNode) &&
3577
3589
  this.checkIsAssignableWith(sourceNode, targetNode));
3578
3590
  }
3579
3591
  #checkIsRelatedTo(sourceNode, targetNode, relation) {
3580
- const sourceType = relation === this.#typeChecker.relation.identity
3592
+ const sourceType = relation === this.typeChecker.relation.identity
3581
3593
  ? this.#simplifyType(this.getType(sourceNode))
3582
3594
  : this.getType(sourceNode);
3583
- const targetType = relation === this.#typeChecker.relation.identity
3595
+ const targetType = relation === this.typeChecker.relation.identity
3584
3596
  ? this.#simplifyType(this.getType(targetNode))
3585
3597
  : this.getType(targetNode);
3586
- return this.#typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
3598
+ return this.typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
3587
3599
  }
3588
3600
  extendsObjectType(type) {
3589
3601
  const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
3590
- return this.#typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
3602
+ return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
3591
3603
  }
3592
3604
  getParameterType(signature, index) {
3593
3605
  const parameter = signature.getDeclaration().parameters[index];
@@ -3608,23 +3620,10 @@ class MatchWorker {
3608
3620
  return signatures;
3609
3621
  }
3610
3622
  getTypeText(node) {
3611
- const type = this.getType(node);
3612
- return this.#typeChecker.typeToString(type);
3623
+ return this.typeChecker.typeToString(this.getType(node));
3613
3624
  }
3614
3625
  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);
3626
+ return this.typeChecker.getTypeAtLocation(node);
3628
3627
  }
3629
3628
  resolveDiagnosticOrigin(symbol, enclosingNode) {
3630
3629
  if (symbol.valueDeclaration != null &&
@@ -3640,7 +3639,7 @@ class MatchWorker {
3640
3639
  #simplifyType(type) {
3641
3640
  if (type.isUnionOrIntersection()) {
3642
3641
  const candidateType = this.#simplifyType(type.types[0]);
3643
- if (type.types.every((type) => this.#typeChecker.isTypeRelatedTo(this.#simplifyType(type), candidateType, this.#typeChecker.relation.identity))) {
3642
+ if (type.types.every((type) => this.typeChecker.isTypeRelatedTo(this.#simplifyType(type), candidateType, this.typeChecker.relation.identity))) {
3644
3643
  return candidateType;
3645
3644
  }
3646
3645
  }
@@ -3648,6 +3647,16 @@ class MatchWorker {
3648
3647
  }
3649
3648
  }
3650
3649
 
3650
+ function isStringOrNumberLiteralType(compiler, type) {
3651
+ return !!(type.flags & compiler.TypeFlags.StringOrNumberLiteral);
3652
+ }
3653
+ function isUnionType(compiler, type) {
3654
+ return !!(type.flags & compiler.TypeFlags.Union);
3655
+ }
3656
+ function isUniqueSymbolType(compiler, type) {
3657
+ return !!(type.flags & compiler.TypeFlags.UniqueESSymbol);
3658
+ }
3659
+
3651
3660
  class ToAcceptProps {
3652
3661
  #compiler;
3653
3662
  #typeChecker;
@@ -3681,7 +3690,7 @@ class ToAcceptProps {
3681
3690
  #isOptionalProperty(symbol) {
3682
3691
  return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
3683
3692
  }
3684
- #checkProperties(matchWorker, sourceType, targetType) {
3693
+ #checkProperties(sourceType, targetType) {
3685
3694
  const check = (sourceType, targetType) => {
3686
3695
  for (const targetProperty of targetType.getProperties()) {
3687
3696
  const targetPropertyName = targetProperty.getName();
@@ -3709,7 +3718,7 @@ class ToAcceptProps {
3709
3718
  }
3710
3719
  return true;
3711
3720
  };
3712
- if (sourceType != null && matchWorker.isUnionType(sourceType)) {
3721
+ if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
3713
3722
  return sourceType.types.some((sourceType) => check(sourceType, targetType));
3714
3723
  }
3715
3724
  return check(sourceType, targetType);
@@ -3777,7 +3786,7 @@ class ToAcceptProps {
3777
3786
  }
3778
3787
  return { diagnostics, isMatch: false };
3779
3788
  };
3780
- if (sourceType != null && matchWorker.isUnionType(sourceType)) {
3789
+ if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
3781
3790
  let accumulator = [];
3782
3791
  const isMatch = sourceType.types.some((sourceType) => {
3783
3792
  const text = matchWorker.assertion.isNot
@@ -3808,7 +3817,7 @@ class ToAcceptProps {
3808
3817
  diagnostics.push(Diagnostic.error(text, origin));
3809
3818
  }
3810
3819
  const targetType = matchWorker.getType(targetNode);
3811
- if (!matchWorker.isObjectType(targetType)) {
3820
+ if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
3812
3821
  const expectedText = "of an object type";
3813
3822
  const text = this.#compiler.isTypeNode(targetNode)
3814
3823
  ? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
@@ -3822,7 +3831,7 @@ class ToAcceptProps {
3822
3831
  }
3823
3832
  const isMatch = signatures.some((signature) => {
3824
3833
  const sourceType = matchWorker.getParameterType(signature, 0);
3825
- return this.#checkProperties(matchWorker, sourceType, targetType);
3834
+ return this.#checkProperties(sourceType, targetType);
3826
3835
  });
3827
3836
  return {
3828
3837
  explain: () => this.#explain(matchWorker, sourceNode, targetNode),
@@ -3890,12 +3899,7 @@ class ToBeApplicable {
3890
3899
  const diagnostics = [];
3891
3900
  if (matchWorker.assertion.abilityDiagnostics) {
3892
3901
  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
- ];
3902
+ const text = [ExpectDiagnosticText.cannotBeApplied(targetText), getDiagnosticMessageText(diagnostic)];
3899
3903
  const origin = DiagnosticOrigin.fromNode(sourceNode);
3900
3904
  diagnostics.push(Diagnostic.error(text.flat(), origin));
3901
3905
  }
@@ -3951,8 +3955,83 @@ class ToBeCallableWith {
3951
3955
  constructor(compiler) {
3952
3956
  this.#compiler = compiler;
3953
3957
  }
3954
- isDiagnosticWithLocation(diagnostic) {
3955
- return diagnostic.start != null;
3958
+ #resolveTargetText(nodes) {
3959
+ if (nodes.length === 0) {
3960
+ return "without arguments";
3961
+ }
3962
+ if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
3963
+ return "with the given arguments";
3964
+ }
3965
+ return `with the given argument${nodes.length === 1 ? "" : "s"}`;
3966
+ }
3967
+ #explain(matchWorker, sourceNode, targetNodes) {
3968
+ const isTypeNode = this.#compiler.isTypeNode(sourceNode);
3969
+ const targetText = this.#resolveTargetText(targetNodes);
3970
+ const diagnostics = [];
3971
+ if (matchWorker.assertion.abilityDiagnostics) {
3972
+ for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3973
+ const text = [ExpectDiagnosticText.isNotCallable(isTypeNode, targetText), getDiagnosticMessageText(diagnostic)];
3974
+ let origin;
3975
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
3976
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
3977
+ }
3978
+ else {
3979
+ origin =
3980
+ targetNodes.length > 0
3981
+ ? DiagnosticOrigin.fromNodes(targetNodes)
3982
+ : DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3983
+ }
3984
+ let related;
3985
+ if (diagnostic.relatedInformation != null) {
3986
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
3987
+ }
3988
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
3989
+ }
3990
+ }
3991
+ else {
3992
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3993
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isTypeNode, targetText), origin));
3994
+ }
3995
+ return diagnostics;
3996
+ }
3997
+ match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
3998
+ let type;
3999
+ if (this.#compiler.isCallExpression(sourceNode)) {
4000
+ type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4001
+ }
4002
+ if (this.#compiler.isArrowFunction(sourceNode) ||
4003
+ this.#compiler.isFunctionDeclaration(sourceNode) ||
4004
+ this.#compiler.isFunctionExpression(sourceNode) ||
4005
+ this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
4006
+ this.#compiler.isIdentifier(sourceNode)) {
4007
+ type = matchWorker.getType(sourceNode);
4008
+ }
4009
+ if (!type || type.getCallSignatures().length === 0) {
4010
+ const text = [];
4011
+ if (this.#compiler.isTypeNode(sourceNode)) {
4012
+ text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
4013
+ }
4014
+ else {
4015
+ text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
4016
+ }
4017
+ if (type != null && type.getConstructSignatures().length > 0) {
4018
+ text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
4019
+ }
4020
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
4021
+ onDiagnostics([Diagnostic.error(text, origin)]);
4022
+ return;
4023
+ }
4024
+ return {
4025
+ explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4026
+ isMatch: !matchWorker.assertion.abilityDiagnostics,
4027
+ };
4028
+ }
4029
+ }
4030
+
4031
+ class ToBeConstructableWith {
4032
+ #compiler;
4033
+ constructor(compiler) {
4034
+ this.#compiler = compiler;
3956
4035
  }
3957
4036
  #resolveTargetText(nodes) {
3958
4037
  if (nodes.length === 0) {
@@ -3970,16 +4049,12 @@ class ToBeCallableWith {
3970
4049
  if (matchWorker.assertion.abilityDiagnostics) {
3971
4050
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3972
4051
  const text = [
3973
- ExpectDiagnosticText.cannotBeCalled(isTypeNode, targetText),
3974
- typeof diagnostic.messageText === "string"
3975
- ? diagnostic.messageText
3976
- : Diagnostic.toMessageText(diagnostic.messageText),
4052
+ ExpectDiagnosticText.isNotConstructable(isTypeNode, targetText),
4053
+ getDiagnosticMessageText(diagnostic),
3977
4054
  ];
3978
4055
  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());
4056
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4057
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
3983
4058
  }
3984
4059
  else {
3985
4060
  origin =
@@ -3996,23 +4071,30 @@ class ToBeCallableWith {
3996
4071
  }
3997
4072
  else {
3998
4073
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3999
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeCalled(isTypeNode, targetText), origin));
4074
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isTypeNode, targetText), origin));
4000
4075
  }
4001
4076
  return diagnostics;
4002
4077
  }
4003
4078
  match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
4004
- let mustBeText;
4005
- if (!(sourceNode.kind === this.#compiler.SyntaxKind.Identifier ||
4006
- sourceNode.kind === this.#compiler.SyntaxKind.ExpressionWithTypeArguments)) {
4007
- mustBeText = "an identifier or instantiation expression";
4008
- }
4009
- if (matchWorker.getType(sourceNode).getCallSignatures().length === 0) {
4010
- mustBeText = "of a function type";
4011
- }
4012
- if (mustBeText != null) {
4013
- const text = this.#compiler.isTypeNode(sourceNode)
4014
- ? ExpectDiagnosticText.typeArgumentMustBe("Source", mustBeText)
4015
- : ExpectDiagnosticText.argumentMustBe("source", mustBeText);
4079
+ let type;
4080
+ if (this.#compiler.isCallExpression(sourceNode)) {
4081
+ type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4082
+ }
4083
+ if (this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
4084
+ this.#compiler.isIdentifier(sourceNode)) {
4085
+ type = matchWorker.getType(sourceNode);
4086
+ }
4087
+ if (!type || type.getConstructSignatures().length === 0) {
4088
+ const text = [];
4089
+ if (this.#compiler.isTypeNode(sourceNode)) {
4090
+ text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
4091
+ }
4092
+ else {
4093
+ text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
4094
+ }
4095
+ if (type != null && type.getCallSignatures().length > 0) {
4096
+ text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeCallableWith()' matcher"));
4097
+ }
4016
4098
  const origin = DiagnosticOrigin.fromNode(sourceNode);
4017
4099
  onDiagnostics([Diagnostic.error(text, origin)]);
4018
4100
  return;
@@ -4033,7 +4115,7 @@ class ToHaveProperty {
4033
4115
  const sourceTypeText = matchWorker.getTypeText(sourceNode);
4034
4116
  const targetType = matchWorker.getType(targetNode);
4035
4117
  let propertyNameText;
4036
- if (matchWorker.isStringOrNumberLiteralType(targetType)) {
4118
+ if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
4037
4119
  propertyNameText = targetType.value.toString();
4038
4120
  }
4039
4121
  else {
@@ -4058,10 +4140,10 @@ class ToHaveProperty {
4058
4140
  }
4059
4141
  const targetType = matchWorker.getType(targetNode);
4060
4142
  let propertyNameText = "";
4061
- if (matchWorker.isStringOrNumberLiteralType(targetType)) {
4143
+ if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
4062
4144
  propertyNameText = targetType.value.toString();
4063
4145
  }
4064
- else if (matchWorker.isUniqueSymbolType(targetType)) {
4146
+ else if (isUniqueSymbolType(this.#compiler, targetType)) {
4065
4147
  propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
4066
4148
  }
4067
4149
  else {
@@ -4155,9 +4237,10 @@ class ToRaiseError {
4155
4237
  if (this.#compiler.isNumericLiteral(targetNode)) {
4156
4238
  return Number.parseInt(targetNode.text, 10) === diagnostic.code;
4157
4239
  }
4158
- const messageText = typeof diagnostic.messageText === "string"
4159
- ? diagnostic.messageText
4160
- : Diagnostic.toMessageText(diagnostic.messageText).join("\n");
4240
+ let messageText = getDiagnosticMessageText(diagnostic);
4241
+ if (Array.isArray(messageText)) {
4242
+ messageText = messageText.join("\n");
4243
+ }
4161
4244
  if (this.#compiler.isRegularExpressionLiteral(targetNode)) {
4162
4245
  const targetRegex = new RegExp(...targetNode.text.slice(1).split("/"));
4163
4246
  return targetRegex.test(messageText);
@@ -4176,6 +4259,7 @@ class ExpectService {
4176
4259
  toBeAssignableTo;
4177
4260
  toBeAssignableWith;
4178
4261
  toBeCallableWith;
4262
+ toBeConstructableWith;
4179
4263
  toHaveProperty;
4180
4264
  toRaiseError;
4181
4265
  constructor(compiler, typeChecker, resolvedConfig) {
@@ -4193,6 +4277,7 @@ class ExpectService {
4193
4277
  this.toBeAssignableTo = new ToBeAssignableTo();
4194
4278
  this.toBeAssignableWith = new ToBeAssignableWith();
4195
4279
  this.toBeCallableWith = new ToBeCallableWith(compiler);
4280
+ this.toBeConstructableWith = new ToBeConstructableWith(compiler);
4196
4281
  this.toHaveProperty = new ToHaveProperty(compiler);
4197
4282
  this.toRaiseError = new ToRaiseError(compiler);
4198
4283
  }
@@ -4220,6 +4305,7 @@ class ExpectService {
4220
4305
  case "toBeApplicable":
4221
4306
  return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
4222
4307
  case "toBeCallableWith":
4308
+ case "toBeConstructableWith":
4223
4309
  case "toRaiseError":
4224
4310
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
4225
4311
  case "toHaveProperty":
@@ -4507,7 +4593,7 @@ class TaskRunner {
4507
4593
  class Runner {
4508
4594
  #eventEmitter = new EventEmitter();
4509
4595
  #resolvedConfig;
4510
- static version = "4.0.0-beta.2";
4596
+ static version = "4.0.0-beta.4";
4511
4597
  constructor(resolvedConfig) {
4512
4598
  this.#resolvedConfig = resolvedConfig;
4513
4599
  }
@@ -4709,4 +4795,4 @@ class Cli {
4709
4795
  }
4710
4796
  }
4711
4797
 
4712
- 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 };
4798
+ 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.2",
3
+ "version": "4.0.0-beta.4",
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.1",
67
67
  "ajv": "8.17.1",
68
68
  "cspell": "8.18.1",
69
69
  "magic-string": "0.30.17",
70
70
  "monocart-coverage-reports": "2.12.3",
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"