tstyche 4.0.1 → 4.1.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
@@ -70,8 +70,7 @@ Here is the list of all matchers:
70
70
  - `.toBeApplicable` ensures that the decorator function can be applied,
71
71
  - `.toBeCallableWith()` checks whether a function is callable with the given arguments,
72
72
  - `.toBeConstructableWith()` checks whether a class is constructable with the given arguments,
73
- - `.toHaveProperty()` looks up keys on an object type,
74
- - `.toRaiseError()` captures the message or code of a type error.
73
+ - `.toHaveProperty()` looks up keys on an object type.
75
74
 
76
75
  ## Runner
77
76
 
@@ -41,6 +41,7 @@ interface CommandLineOptions {
41
41
  }
42
42
  interface ConfigFileOptions {
43
43
  checkSourceFiles?: boolean;
44
+ checkSuppressedErrors?: boolean;
44
45
  failFast?: boolean;
45
46
  plugins?: Array<string>;
46
47
  rejectAnyType?: boolean;
@@ -177,7 +178,7 @@ declare class Diagnostic {
177
178
  }): this;
178
179
  static error(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
179
180
  extendWith(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
180
- static fromDiagnostics(diagnostics: Array<ts.Diagnostic>): Array<Diagnostic>;
181
+ static fromDiagnostics(diagnostics: Array<ts.Diagnostic>, sourceFile?: ts.SourceFile): Array<Diagnostic>;
181
182
  static warning(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
182
183
  }
183
184
 
@@ -231,7 +232,7 @@ declare enum TestTreeNodeFlags {
231
232
  declare class WhenNode extends TestTreeNode {
232
233
  actionNode: ts.CallExpression;
233
234
  actionNameNode: ts.PropertyAccessExpression;
234
- abilityDiagnostics: Set<ts.Diagnostic> | undefined;
235
+ abilityDiagnostics: Set<ts.Diagnostic>;
235
236
  target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
236
237
  constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, actionNode: ts.CallExpression, actionNameNode: ts.PropertyAccessExpression);
237
238
  }
@@ -248,17 +249,28 @@ declare class TestTreeNode {
248
249
  getDirectiveRanges(compiler: typeof ts): DirectiveRanges | undefined;
249
250
  }
250
251
 
252
+ interface SuppressedError {
253
+ directive: TextRange;
254
+ ignore: boolean;
255
+ argument?: TextRange;
256
+ diagnostics: Array<ts.Diagnostic>;
257
+ }
258
+ type SuppressedErrors = Array<SuppressedError> & {
259
+ sourceFile: ts.SourceFile;
260
+ };
261
+
251
262
  declare class TestTree {
252
263
  children: Array<TestTreeNode | AssertionNode | WhenNode>;
253
264
  diagnostics: Set<ts.Diagnostic>;
254
265
  hasOnly: boolean;
255
266
  sourceFile: ts.SourceFile;
267
+ suppressedErrors: SuppressedErrors | undefined;
256
268
  constructor(diagnostics: Set<ts.Diagnostic>, sourceFile: ts.SourceFile);
257
269
  getDirectiveRanges(compiler: typeof ts): DirectiveRanges | undefined;
258
270
  }
259
271
 
260
272
  declare class AssertionNode extends TestTreeNode {
261
- abilityDiagnostics: Set<ts.Diagnostic> | undefined;
273
+ abilityDiagnostics: Set<ts.Diagnostic>;
262
274
  isNot: boolean;
263
275
  matcherNode: ts.CallExpression | ts.Decorator;
264
276
  matcherNameNode: ts.PropertyAccessExpression;
@@ -759,6 +771,10 @@ declare class Store {
759
771
  static validateTag(tag: string): Promise<boolean | undefined>;
760
772
  }
761
773
 
774
+ declare class SuppressedService {
775
+ match(suppressedErrors: SuppressedErrors, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>): void;
776
+ }
777
+
762
778
  declare class Version {
763
779
  #private;
764
780
  static isGreaterThan(source: string, target: string): boolean;
@@ -794,5 +810,5 @@ declare class WhenService {
794
810
  action(when: WhenNode): void;
795
811
  }
796
812
 
797
- export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, ScribblerJsx, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
798
- export type { CodeFrameOptions, CommandLineOptions, ConfigFileOptions, DiagnosticsHandler, DirectiveRange, DirectiveRanges, EnvironmentOptions, Event, EventHandler, FileWatchHandler, InlineConfig, InputHandler, ItemDefinition, MatchResult, OptionDefinition, Plugin, Reporter, ReporterEvent, ResolvedConfig, ScribblerOptions, SelectHookContext, TargetResultStatus, TaskResultStatus, TypeChecker, WatchHandler, WatcherOptions };
813
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, ScribblerJsx, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, SuppressedService, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
814
+ export type { CodeFrameOptions, CommandLineOptions, ConfigFileOptions, DiagnosticsHandler, DirectiveRange, DirectiveRanges, EnvironmentOptions, Event, EventHandler, FileWatchHandler, InlineConfig, InputHandler, ItemDefinition, MatchResult, OptionDefinition, Plugin, Reporter, ReporterEvent, ResolvedConfig, ScribblerOptions, SelectHookContext, SuppressedError, SuppressedErrors, TargetResultStatus, TaskResultStatus, TextRange, TypeChecker, WatchHandler, WatcherOptions };
package/build/tstyche.js CHANGED
@@ -87,12 +87,12 @@ class Diagnostic {
87
87
  extendWith(text, origin) {
88
88
  return new Diagnostic([this.text, text].flat(), this.category, origin ?? this.origin);
89
89
  }
90
- static fromDiagnostics(diagnostics) {
90
+ static fromDiagnostics(diagnostics, sourceFile) {
91
91
  return diagnostics.map((diagnostic) => {
92
92
  const code = `ts(${diagnostic.code})`;
93
93
  let origin;
94
- if (diagnostic.file != null && diagnostic.start != null && diagnostic.length != null) {
95
- origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
94
+ if (isDiagnosticWithLocation(diagnostic)) {
95
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceFile ?? diagnostic.file);
96
96
  }
97
97
  let related;
98
98
  if (diagnostic.relatedInformation != null) {
@@ -910,6 +910,12 @@ class Options {
910
910
  group: OptionGroup.ConfigFile,
911
911
  name: "checkSourceFiles",
912
912
  },
913
+ {
914
+ brand: OptionBrand.Boolean,
915
+ description: "Check errors silenced by '// @ts-expect-error' directives.",
916
+ group: OptionGroup.ConfigFile,
917
+ name: "checkSuppressedErrors",
918
+ },
913
919
  {
914
920
  brand: OptionBrand.String,
915
921
  description: "The path to a TSTyche configuration file.",
@@ -1394,6 +1400,7 @@ class ConfigParser {
1394
1400
 
1395
1401
  const defaultOptions = {
1396
1402
  checkSourceFiles: true,
1403
+ checkSuppressedErrors: false,
1397
1404
  failFast: false,
1398
1405
  plugins: [],
1399
1406
  rejectAnyType: true,
@@ -2130,7 +2137,9 @@ function describeNameText(name, indent = 0) {
2130
2137
  function BreadcrumbsText({ ancestor }) {
2131
2138
  const text = [];
2132
2139
  while ("name" in ancestor) {
2133
- text.push(ancestor.name);
2140
+ if (ancestor.name !== "") {
2141
+ text.push(ancestor.name);
2142
+ }
2134
2143
  ancestor = ancestor.parent;
2135
2144
  }
2136
2145
  text.push("");
@@ -3028,7 +3037,7 @@ class TestTreeNode {
3028
3037
  }
3029
3038
 
3030
3039
  class AssertionNode extends TestTreeNode {
3031
- abilityDiagnostics;
3040
+ abilityDiagnostics = new Set();
3032
3041
  isNot;
3033
3042
  matcherNode;
3034
3043
  matcherNameNode;
@@ -3065,34 +3074,18 @@ function nodeIsChildOfExpressionStatement(compiler, node) {
3065
3074
 
3066
3075
  class AbilityLayer {
3067
3076
  #compiler;
3077
+ #expectErrorRegex = /^( *)(\/\/ *@ts-expect-error)(!?)(:? *)(.*)?$/gim;
3068
3078
  #filePath = "";
3069
3079
  #nodes = [];
3070
3080
  #projectService;
3071
3081
  #resolvedConfig;
3082
+ #suppressedErrorsMap;
3072
3083
  #text = "";
3073
3084
  constructor(compiler, projectService, resolvedConfig) {
3074
3085
  this.#compiler = compiler;
3075
3086
  this.#projectService = projectService;
3076
3087
  this.#resolvedConfig = resolvedConfig;
3077
3088
  }
3078
- #getErasedRangeText(range) {
3079
- if (this.#text.indexOf("\n", range.start) >= range.end) {
3080
- return " ".repeat(range.end - range.start);
3081
- }
3082
- const text = [];
3083
- for (let index = range.start; index < range.end; index++) {
3084
- const character = this.#text.charAt(index);
3085
- switch (character) {
3086
- case "\n":
3087
- case "\r":
3088
- text.push(character);
3089
- break;
3090
- default:
3091
- text.push(" ");
3092
- }
3093
- }
3094
- return text.join("");
3095
- }
3096
3089
  #addRanges(node, ranges) {
3097
3090
  this.#nodes.push(node);
3098
3091
  for (const range of ranges) {
@@ -3102,25 +3095,78 @@ class AbilityLayer {
3102
3095
  this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
3103
3096
  }
3104
3097
  }
3098
+ #belongsToNode(diagnostic) {
3099
+ for (const node of this.#nodes) {
3100
+ if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
3101
+ node.abilityDiagnostics.add(diagnostic);
3102
+ return true;
3103
+ }
3104
+ }
3105
+ return false;
3106
+ }
3107
+ #belongsToDirective(diagnostic) {
3108
+ if (!isDiagnosticWithLocation(diagnostic)) {
3109
+ return;
3110
+ }
3111
+ const { file, start } = diagnostic;
3112
+ const lineMap = file.getLineStarts();
3113
+ let line = this.#compiler.getLineAndCharacterOfPosition(file, start).line - 1;
3114
+ while (line >= 0) {
3115
+ const suppressedError = this.#suppressedErrorsMap?.get(line);
3116
+ if (suppressedError != null) {
3117
+ suppressedError.diagnostics.push(diagnostic);
3118
+ break;
3119
+ }
3120
+ const lineText = file.text.slice(lineMap[line], lineMap[line + 1]).trim();
3121
+ if (lineText !== "" && !lineText.startsWith("//")) {
3122
+ break;
3123
+ }
3124
+ line--;
3125
+ }
3126
+ }
3127
+ #collectSuppressedErrors() {
3128
+ const ranges = [];
3129
+ for (const match of this.#text.matchAll(this.#expectErrorRegex)) {
3130
+ const offsetText = match?.[1];
3131
+ const directiveText = match?.[2];
3132
+ const ignoreText = match?.[3];
3133
+ const argumentSeparatorText = match?.[4];
3134
+ const argumentText = match?.[5]?.split(/--+/)[0]?.trimEnd();
3135
+ if (typeof offsetText !== "string" || !directiveText) {
3136
+ continue;
3137
+ }
3138
+ const start = match.index + offsetText.length;
3139
+ const range = {
3140
+ directive: { start, end: start + directiveText.length, text: directiveText },
3141
+ ignore: ignoreText === "!",
3142
+ diagnostics: [],
3143
+ };
3144
+ if (typeof argumentSeparatorText === "string" && typeof argumentText === "string") {
3145
+ const start = range.directive.end + argumentSeparatorText.length;
3146
+ range.argument = { start, end: start + argumentText.length, text: argumentText };
3147
+ }
3148
+ ranges.push(range);
3149
+ }
3150
+ return ranges;
3151
+ }
3105
3152
  close() {
3106
- if (this.#nodes.length > 0) {
3153
+ if (this.#nodes.length > 0 || this.#suppressedErrorsMap != null) {
3107
3154
  this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
3108
3155
  const languageService = this.#projectService.getLanguageService(this.#filePath);
3109
- const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
3110
- for (const node of this.#nodes.reverse()) {
3156
+ const diagnostics = languageService?.getSemanticDiagnostics(this.#filePath);
3157
+ if (diagnostics != null) {
3158
+ this.#nodes.reverse();
3111
3159
  for (const diagnostic of diagnostics) {
3112
- if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
3113
- if (!node.abilityDiagnostics) {
3114
- node.abilityDiagnostics = new Set();
3115
- }
3116
- node.abilityDiagnostics.add(diagnostic);
3117
- diagnostics.delete(diagnostic);
3160
+ if (this.#belongsToNode(diagnostic)) {
3161
+ continue;
3118
3162
  }
3163
+ this.#belongsToDirective(diagnostic);
3119
3164
  }
3120
3165
  }
3121
3166
  }
3122
3167
  this.#filePath = "";
3123
3168
  this.#nodes = [];
3169
+ this.#suppressedErrorsMap = undefined;
3124
3170
  this.#text = "";
3125
3171
  }
3126
3172
  #eraseTrailingComma(node, parent) {
@@ -3128,24 +3174,23 @@ class AbilityLayer {
3128
3174
  this.#addRanges(parent, [{ start: node.end - 1, end: node.end }]);
3129
3175
  }
3130
3176
  }
3131
- handleWhen(whenNode) {
3132
- const whenStart = whenNode.node.getStart();
3133
- const whenExpressionEnd = whenNode.node.expression.getEnd();
3134
- const whenEnd = whenNode.node.getEnd();
3135
- const actionNameEnd = whenNode.actionNameNode.getEnd();
3136
- switch (whenNode.actionNameNode.name.text) {
3137
- case "isCalledWith":
3138
- this.#eraseTrailingComma(whenNode.target, whenNode);
3139
- this.#addRanges(whenNode, [
3140
- {
3141
- start: whenStart,
3142
- end: whenExpressionEnd,
3143
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3144
- },
3145
- { start: whenEnd, end: actionNameEnd },
3146
- ]);
3147
- break;
3177
+ #getErasedRangeText(range) {
3178
+ if (this.#text.indexOf("\n", range.start) >= range.end) {
3179
+ return " ".repeat(range.end - range.start);
3180
+ }
3181
+ const text = [];
3182
+ for (let index = range.start; index < range.end; index++) {
3183
+ const character = this.#text.charAt(index);
3184
+ switch (character) {
3185
+ case "\n":
3186
+ case "\r":
3187
+ text.push(character);
3188
+ break;
3189
+ default:
3190
+ text.push(" ");
3191
+ }
3148
3192
  }
3193
+ return text.join("");
3149
3194
  }
3150
3195
  handleAssertion(assertionNode) {
3151
3196
  const expectStart = assertionNode.node.getStart();
@@ -3183,9 +3228,45 @@ class AbilityLayer {
3183
3228
  break;
3184
3229
  }
3185
3230
  }
3186
- open(sourceFile) {
3187
- this.#filePath = sourceFile.fileName;
3188
- this.#text = sourceFile.text;
3231
+ #handleSuppressedErrors(testTree) {
3232
+ const suppressedErrors = this.#collectSuppressedErrors();
3233
+ if (this.#resolvedConfig.checkSuppressedErrors) {
3234
+ testTree.suppressedErrors = Object.assign(suppressedErrors, { sourceFile: testTree.sourceFile });
3235
+ this.#suppressedErrorsMap = new Map();
3236
+ }
3237
+ for (const suppressedError of suppressedErrors) {
3238
+ const { start, end } = suppressedError.directive;
3239
+ const rangeText = this.#getErasedRangeText({ start: start + 2, end });
3240
+ this.#text = `${this.#text.slice(0, start + 2)}${rangeText}${this.#text.slice(end)}`;
3241
+ if (this.#suppressedErrorsMap != null) {
3242
+ const { line } = testTree.sourceFile.getLineAndCharacterOfPosition(start);
3243
+ this.#suppressedErrorsMap.set(line, suppressedError);
3244
+ }
3245
+ }
3246
+ }
3247
+ handleWhen(whenNode) {
3248
+ const whenStart = whenNode.node.getStart();
3249
+ const whenExpressionEnd = whenNode.node.expression.getEnd();
3250
+ const whenEnd = whenNode.node.getEnd();
3251
+ const actionNameEnd = whenNode.actionNameNode.getEnd();
3252
+ switch (whenNode.actionNameNode.name.text) {
3253
+ case "isCalledWith":
3254
+ this.#eraseTrailingComma(whenNode.target, whenNode);
3255
+ this.#addRanges(whenNode, [
3256
+ {
3257
+ start: whenStart,
3258
+ end: whenExpressionEnd,
3259
+ replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3260
+ },
3261
+ { start: whenEnd, end: actionNameEnd },
3262
+ ]);
3263
+ break;
3264
+ }
3265
+ }
3266
+ open(testTree) {
3267
+ this.#filePath = testTree.sourceFile.fileName;
3268
+ this.#text = testTree.sourceFile.text;
3269
+ this.#handleSuppressedErrors(testTree);
3189
3270
  }
3190
3271
  }
3191
3272
 
@@ -3312,6 +3393,7 @@ class TestTree {
3312
3393
  diagnostics;
3313
3394
  hasOnly = false;
3314
3395
  sourceFile;
3396
+ suppressedErrors;
3315
3397
  constructor(diagnostics, sourceFile) {
3316
3398
  this.diagnostics = diagnostics;
3317
3399
  this.sourceFile = sourceFile;
@@ -3324,7 +3406,7 @@ class TestTree {
3324
3406
  class WhenNode extends TestTreeNode {
3325
3407
  actionNode;
3326
3408
  actionNameNode;
3327
- abilityDiagnostics;
3409
+ abilityDiagnostics = new Set();
3328
3410
  target;
3329
3411
  constructor(compiler, brand, node, parent, flags, actionNode, actionNameNode) {
3330
3412
  super(compiler, brand, node, parent, flags);
@@ -3423,7 +3505,7 @@ class CollectService {
3423
3505
  createTestTree(sourceFile, semanticDiagnostics = []) {
3424
3506
  const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3425
3507
  EventEmitter.dispatch(["collect:start", { tree: testTree }]);
3426
- this.#abilityLayer.open(sourceFile);
3508
+ this.#abilityLayer.open(testTree);
3427
3509
  this.#identifierLookup.open();
3428
3510
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3429
3511
  this.#abilityLayer.close();
@@ -3560,6 +3642,9 @@ class ProjectService {
3560
3642
  defaultCompilerOptions.allowImportingTsExtensions = true;
3561
3643
  defaultCompilerOptions.verbatimModuleSyntax = true;
3562
3644
  }
3645
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.6")) {
3646
+ defaultCompilerOptions.noUncheckedSideEffectImports = true;
3647
+ }
3563
3648
  return defaultCompilerOptions;
3564
3649
  }
3565
3650
  getDefaultProject(filePath) {
@@ -3633,6 +3718,60 @@ class ProjectService {
3633
3718
  }
3634
3719
  }
3635
3720
 
3721
+ class SuppressedDiagnosticText {
3722
+ static directiveRequires() {
3723
+ return [
3724
+ "Directive requires an argument.",
3725
+ "Add a fragment of the expected error message after the directive.",
3726
+ "To ignore the directive, append a '!' character after it.",
3727
+ ];
3728
+ }
3729
+ static messageDidNotMatch() {
3730
+ return "The diagnostic message did not match.";
3731
+ }
3732
+ static onlySingleError() {
3733
+ return "Only a single error can be suppressed.";
3734
+ }
3735
+ static suppressedError(count = 1) {
3736
+ return `The suppressed error${count === 1 ? "" : "s"}:`;
3737
+ }
3738
+ }
3739
+
3740
+ class SuppressedService {
3741
+ match(suppressedErrors, onDiagnostics) {
3742
+ for (const suppressedError of suppressedErrors) {
3743
+ if (suppressedError.diagnostics.length === 0 || suppressedError.ignore) {
3744
+ continue;
3745
+ }
3746
+ if (!suppressedError.argument?.text) {
3747
+ const text = SuppressedDiagnosticText.directiveRequires();
3748
+ const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, suppressedErrors.sourceFile);
3749
+ onDiagnostics([Diagnostic.error(text, origin)]);
3750
+ continue;
3751
+ }
3752
+ const related = [
3753
+ Diagnostic.error(SuppressedDiagnosticText.suppressedError(suppressedError.diagnostics.length)),
3754
+ ...Diagnostic.fromDiagnostics(suppressedError.diagnostics, suppressedErrors.sourceFile),
3755
+ ];
3756
+ if (suppressedError.diagnostics.length > 1) {
3757
+ const text = [SuppressedDiagnosticText.onlySingleError()];
3758
+ const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, suppressedErrors.sourceFile);
3759
+ onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
3760
+ continue;
3761
+ }
3762
+ let messageText = getDiagnosticMessageText(suppressedError.diagnostics[0]);
3763
+ if (Array.isArray(messageText)) {
3764
+ messageText = messageText.join("\n");
3765
+ }
3766
+ if (!messageText.includes(suppressedError.argument.text)) {
3767
+ const text = [SuppressedDiagnosticText.messageDidNotMatch()];
3768
+ const origin = new DiagnosticOrigin(suppressedError.argument.start, suppressedError.argument.end, suppressedErrors.sourceFile);
3769
+ onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
3770
+ }
3771
+ }
3772
+ }
3773
+ }
3774
+
3636
3775
  var RunMode;
3637
3776
  (function (RunMode) {
3638
3777
  RunMode[RunMode["Normal"] = 0] = "Normal";
@@ -4117,7 +4256,7 @@ class ToBeApplicable {
4117
4256
  #explain(matchWorker, sourceNode) {
4118
4257
  const targetText = this.#resolveTargetText(matchWorker.assertion.matcherNode.parent);
4119
4258
  const diagnostics = [];
4120
- if (matchWorker.assertion.abilityDiagnostics) {
4259
+ if (matchWorker.assertion.abilityDiagnostics.size > 0) {
4121
4260
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4122
4261
  const text = [ExpectDiagnosticText.cannotBeApplied(targetText), getDiagnosticMessageText(diagnostic)];
4123
4262
  const origin = DiagnosticOrigin.fromNode(sourceNode);
@@ -4143,7 +4282,7 @@ class ToBeApplicable {
4143
4282
  }
4144
4283
  return {
4145
4284
  explain: () => this.#explain(matchWorker, sourceNode),
4146
- isMatch: !matchWorker.assertion.abilityDiagnostics,
4285
+ isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4147
4286
  };
4148
4287
  }
4149
4288
  }
@@ -4170,69 +4309,75 @@ class ToBeAssignableWith extends RelationMatcherBase {
4170
4309
  }
4171
4310
  }
4172
4311
 
4173
- class ToBeCallableWith {
4174
- #compiler;
4312
+ class AbilityMatcherBase {
4313
+ compiler;
4175
4314
  constructor(compiler) {
4176
- this.#compiler = compiler;
4315
+ this.compiler = compiler;
4177
4316
  }
4178
4317
  #resolveTargetText(nodes) {
4179
4318
  if (nodes.length === 0) {
4180
4319
  return "without arguments";
4181
4320
  }
4182
- if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
4321
+ if (nodes.length === 1 && nodes[0]?.kind === this.compiler.SyntaxKind.SpreadElement) {
4183
4322
  return "with the given arguments";
4184
4323
  }
4185
4324
  return `with the given argument${nodes.length === 1 ? "" : "s"}`;
4186
4325
  }
4187
- #explain(matchWorker, sourceNode, targetNodes) {
4188
- const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4326
+ explain(matchWorker, sourceNode, targetNodes) {
4327
+ const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
4189
4328
  const targetText = this.#resolveTargetText(targetNodes);
4190
4329
  const diagnostics = [];
4191
- if (matchWorker.assertion.abilityDiagnostics) {
4330
+ if (matchWorker.assertion.abilityDiagnostics.size > 0) {
4192
4331
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4193
- const text = [
4194
- ExpectDiagnosticText.isNotCallable(isExpression, targetText),
4195
- getDiagnosticMessageText(diagnostic),
4196
- ];
4197
4332
  let origin;
4198
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4199
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
4333
+ const text = [];
4334
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, sourceNode)) {
4335
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertion);
4336
+ text.push(getDiagnosticMessageText(diagnostic));
4200
4337
  }
4201
4338
  else {
4202
- origin =
4203
- targetNodes.length > 0
4204
- ? DiagnosticOrigin.fromNodes(targetNodes)
4205
- : DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4339
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4340
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertion);
4341
+ }
4342
+ else {
4343
+ origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4344
+ }
4345
+ text.push(this.explainNotText(isExpression, targetText), getDiagnosticMessageText(diagnostic));
4206
4346
  }
4207
4347
  let related;
4208
4348
  if (diagnostic.relatedInformation != null) {
4209
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4349
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, sourceNode.getSourceFile());
4210
4350
  }
4211
4351
  diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4212
4352
  }
4213
4353
  }
4214
4354
  else {
4215
4355
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4216
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isExpression, targetText), origin));
4356
+ diagnostics.push(Diagnostic.error(this.explainText(isExpression, targetText), origin));
4217
4357
  }
4218
4358
  return diagnostics;
4219
4359
  }
4360
+ }
4361
+
4362
+ class ToBeCallableWith extends AbilityMatcherBase {
4363
+ explainText = ExpectDiagnosticText.isCallable;
4364
+ explainNotText = ExpectDiagnosticText.isNotCallable;
4220
4365
  match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
4221
4366
  let type;
4222
- if (this.#compiler.isCallExpression(sourceNode)) {
4367
+ if (this.compiler.isCallExpression(sourceNode)) {
4223
4368
  type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4224
4369
  }
4225
- if (this.#compiler.isArrowFunction(sourceNode) ||
4226
- this.#compiler.isFunctionDeclaration(sourceNode) ||
4227
- this.#compiler.isFunctionExpression(sourceNode) ||
4228
- this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
4229
- this.#compiler.isIdentifier(sourceNode) ||
4230
- this.#compiler.isPropertyAccessExpression(sourceNode)) {
4370
+ if (this.compiler.isArrowFunction(sourceNode) ||
4371
+ this.compiler.isFunctionDeclaration(sourceNode) ||
4372
+ this.compiler.isFunctionExpression(sourceNode) ||
4373
+ this.compiler.isExpressionWithTypeArguments(sourceNode) ||
4374
+ this.compiler.isIdentifier(sourceNode) ||
4375
+ this.compiler.isPropertyAccessExpression(sourceNode)) {
4231
4376
  type = matchWorker.getType(sourceNode);
4232
4377
  }
4233
4378
  if (!type || type.getCallSignatures().length === 0) {
4234
4379
  const text = [];
4235
- if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
4380
+ if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
4236
4381
  text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
4237
4382
  }
4238
4383
  else {
@@ -4246,72 +4391,28 @@ class ToBeCallableWith {
4246
4391
  return;
4247
4392
  }
4248
4393
  return {
4249
- explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4250
- isMatch: !matchWorker.assertion.abilityDiagnostics,
4394
+ explain: () => this.explain(matchWorker, sourceNode, targetNodes),
4395
+ isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4251
4396
  };
4252
4397
  }
4253
4398
  }
4254
4399
 
4255
- class ToBeConstructableWith {
4256
- #compiler;
4257
- constructor(compiler) {
4258
- this.#compiler = compiler;
4259
- }
4260
- #resolveTargetText(nodes) {
4261
- if (nodes.length === 0) {
4262
- return "without arguments";
4263
- }
4264
- if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
4265
- return "with the given arguments";
4266
- }
4267
- return `with the given argument${nodes.length === 1 ? "" : "s"}`;
4268
- }
4269
- #explain(matchWorker, sourceNode, targetNodes) {
4270
- const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4271
- const targetText = this.#resolveTargetText(targetNodes);
4272
- const diagnostics = [];
4273
- if (matchWorker.assertion.abilityDiagnostics) {
4274
- for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4275
- const text = [
4276
- ExpectDiagnosticText.isNotConstructable(isExpression, targetText),
4277
- getDiagnosticMessageText(diagnostic),
4278
- ];
4279
- let origin;
4280
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4281
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
4282
- }
4283
- else {
4284
- origin =
4285
- targetNodes.length > 0
4286
- ? DiagnosticOrigin.fromNodes(targetNodes)
4287
- : DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4288
- }
4289
- let related;
4290
- if (diagnostic.relatedInformation != null) {
4291
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4292
- }
4293
- diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4294
- }
4295
- }
4296
- else {
4297
- const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4298
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isExpression, targetText), origin));
4299
- }
4300
- return diagnostics;
4301
- }
4400
+ class ToBeConstructableWith extends AbilityMatcherBase {
4401
+ explainText = ExpectDiagnosticText.isConstructable;
4402
+ explainNotText = ExpectDiagnosticText.isNotConstructable;
4302
4403
  match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
4303
4404
  let type;
4304
- if (this.#compiler.isCallExpression(sourceNode)) {
4405
+ if (this.compiler.isCallExpression(sourceNode)) {
4305
4406
  type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4306
4407
  }
4307
- if (this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
4308
- this.#compiler.isIdentifier(sourceNode) ||
4309
- this.#compiler.isPropertyAccessExpression(sourceNode)) {
4408
+ if (this.compiler.isExpressionWithTypeArguments(sourceNode) ||
4409
+ this.compiler.isIdentifier(sourceNode) ||
4410
+ this.compiler.isPropertyAccessExpression(sourceNode)) {
4310
4411
  type = matchWorker.getType(sourceNode);
4311
4412
  }
4312
4413
  if (!type || type.getConstructSignatures().length === 0) {
4313
4414
  const text = [];
4314
- if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
4415
+ if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
4315
4416
  text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
4316
4417
  }
4317
4418
  else {
@@ -4325,8 +4426,8 @@ class ToBeConstructableWith {
4325
4426
  return;
4326
4427
  }
4327
4428
  return {
4328
- explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4329
- isMatch: !matchWorker.assertion.abilityDiagnostics,
4429
+ explain: () => this.explain(matchWorker, sourceNode, targetNodes),
4430
+ isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4330
4431
  };
4331
4432
  }
4332
4433
  }
@@ -4633,7 +4734,7 @@ class WhenService {
4633
4734
  this.#onActionIsNotSupported(actionNameText, when, this.#onDiagnostics);
4634
4735
  return;
4635
4736
  }
4636
- if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
4737
+ if (when.abilityDiagnostics.size > 0) {
4637
4738
  const diagnostics = [];
4638
4739
  for (const diagnostic of when.abilityDiagnostics) {
4639
4740
  if (isDiagnosticWithLocation(diagnostic)) {
@@ -4829,8 +4930,9 @@ class TestTreeWalker {
4829
4930
  class TaskRunner {
4830
4931
  #collectService;
4831
4932
  #compiler;
4832
- #resolvedConfig;
4833
4933
  #projectService;
4934
+ #resolvedConfig;
4935
+ #suppressedService = new SuppressedService();
4834
4936
  constructor(compiler, resolvedConfig) {
4835
4937
  this.#compiler = compiler;
4836
4938
  this.#resolvedConfig = resolvedConfig;
@@ -4871,6 +4973,11 @@ class TaskRunner {
4871
4973
  if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
4872
4974
  runMode |= RunMode.Skip;
4873
4975
  }
4976
+ if (testTree.suppressedErrors != null) {
4977
+ this.#suppressedService.match(testTree.suppressedErrors, (diagnostics) => {
4978
+ this.#onDiagnostics(diagnostics, taskResult);
4979
+ });
4980
+ }
4874
4981
  if (inlineConfig?.template) {
4875
4982
  if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
4876
4983
  this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
@@ -4915,7 +5022,7 @@ class TaskRunner {
4915
5022
  class Runner {
4916
5023
  #eventEmitter = new EventEmitter();
4917
5024
  #resolvedConfig;
4918
- static version = "4.0.1";
5025
+ static version = "4.1.0";
4919
5026
  constructor(resolvedConfig) {
4920
5027
  this.#resolvedConfig = resolvedConfig;
4921
5028
  }
@@ -5115,4 +5222,4 @@ class Cli {
5115
5222
  }
5116
5223
  }
5117
5224
 
5118
- export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
5225
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, SuppressedService, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Everything You Need for Type Testing.",
5
5
  "keywords": [
6
6
  "typescript",