tstyche 4.0.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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;
@@ -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;
@@ -546,6 +558,11 @@ declare class ExpectService {
546
558
  match(assertion: AssertionNode, onDiagnostics: DiagnosticsHandler<Diagnostic | Array<Diagnostic>>): MatchResult | undefined;
547
559
  }
548
560
 
561
+ declare class Glob {
562
+ #private;
563
+ static toRegex(patterns: Array<string>, target: "directories" | "files"): RegExp;
564
+ }
565
+
549
566
  declare class CancellationHandler implements EventHandler {
550
567
  #private;
551
568
  constructor(cancellationToken: CancellationToken, cancellationReason: CancellationReason);
@@ -759,6 +776,10 @@ declare class Store {
759
776
  static validateTag(tag: string): Promise<boolean | undefined>;
760
777
  }
761
778
 
779
+ declare class SuppressedService {
780
+ match(suppressedErrors: SuppressedErrors, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>): void;
781
+ }
782
+
762
783
  declare class Version {
763
784
  #private;
764
785
  static isGreaterThan(source: string, target: string): boolean;
@@ -794,5 +815,5 @@ declare class WhenService {
794
815
  action(when: WhenNode): void;
795
816
  }
796
817
 
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, TextRange, TypeChecker, WatchHandler, WatcherOptions };
818
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, Glob, 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 };
819
+ 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
@@ -96,7 +96,7 @@ class Diagnostic {
96
96
  }
97
97
  let related;
98
98
  if (diagnostic.relatedInformation != null) {
99
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, sourceFile);
99
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
100
100
  }
101
101
  const text = getDiagnosticMessageText(diagnostic);
102
102
  return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
@@ -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,
@@ -2735,7 +2742,64 @@ class InputService {
2735
2742
  }
2736
2743
  }
2737
2744
 
2738
- class GlobPattern {
2745
+ class Braces {
2746
+ static expand(text) {
2747
+ const start = text.indexOf("{");
2748
+ if (start === -1) {
2749
+ return [text];
2750
+ }
2751
+ let position = start;
2752
+ let depth = 0;
2753
+ for (position; position < text.length; position++) {
2754
+ if (text[position] === "{") {
2755
+ depth++;
2756
+ }
2757
+ else if (text[position] === "}") {
2758
+ depth--;
2759
+ }
2760
+ if (depth === 0) {
2761
+ break;
2762
+ }
2763
+ }
2764
+ if (depth !== 0) {
2765
+ return [text];
2766
+ }
2767
+ const before = text.slice(0, start);
2768
+ const options = Braces.#splitOptions(text.slice(start + 1, position));
2769
+ const after = text.slice(position + 1);
2770
+ const result = [];
2771
+ for (const option of options) {
2772
+ for (const expanded of Braces.expand(option + after)) {
2773
+ result.push(before + expanded);
2774
+ }
2775
+ }
2776
+ return result;
2777
+ }
2778
+ static #splitOptions(optionText) {
2779
+ const options = [];
2780
+ let current = "";
2781
+ let depth = 0;
2782
+ for (const character of optionText) {
2783
+ if (character === "," && depth === 0) {
2784
+ options.push(current);
2785
+ current = "";
2786
+ }
2787
+ else {
2788
+ if (character === "{") {
2789
+ depth++;
2790
+ }
2791
+ if (character === "}") {
2792
+ depth--;
2793
+ }
2794
+ current += character;
2795
+ }
2796
+ }
2797
+ options.push(current);
2798
+ return options;
2799
+ }
2800
+ }
2801
+
2802
+ class Glob {
2739
2803
  static #reservedCharacterRegex = /[^\w\s/]/g;
2740
2804
  static #parse(pattern, usageTarget) {
2741
2805
  const segments = pattern.split("/");
@@ -2754,7 +2818,7 @@ class GlobPattern {
2754
2818
  optionalSegmentCount++;
2755
2819
  }
2756
2820
  resultPattern += "\\/";
2757
- const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
2821
+ const segmentPattern = segment.replace(Glob.#reservedCharacterRegex, Glob.#replaceReservedCharacter);
2758
2822
  if (segmentPattern !== segment) {
2759
2823
  resultPattern += "(?!(node_modules)(\\/|$))";
2760
2824
  }
@@ -2774,7 +2838,10 @@ class GlobPattern {
2774
2838
  }
2775
2839
  }
2776
2840
  static toRegex(patterns, target) {
2777
- const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, target)})`).join("|");
2841
+ const patternText = patterns
2842
+ .flatMap((pattern) => Braces.expand(pattern))
2843
+ .map((pattern) => `(${Glob.#parse(pattern, target)})`)
2844
+ .join("|");
2778
2845
  return new RegExp(`^(${patternText})$`);
2779
2846
  }
2780
2847
  }
@@ -2832,8 +2899,8 @@ class Select {
2832
2899
  let matchPatterns = Select.#patternsCache.get(globPatterns);
2833
2900
  if (!matchPatterns) {
2834
2901
  matchPatterns = {
2835
- includedDirectory: GlobPattern.toRegex(globPatterns, "directories"),
2836
- includedFile: GlobPattern.toRegex(globPatterns, "files"),
2902
+ includedDirectory: Glob.toRegex(globPatterns, "directories"),
2903
+ includedFile: Glob.toRegex(globPatterns, "files"),
2837
2904
  };
2838
2905
  Select.#patternsCache.set(globPatterns, matchPatterns);
2839
2906
  }
@@ -3030,7 +3097,7 @@ class TestTreeNode {
3030
3097
  }
3031
3098
 
3032
3099
  class AssertionNode extends TestTreeNode {
3033
- abilityDiagnostics;
3100
+ abilityDiagnostics = new Set();
3034
3101
  isNot;
3035
3102
  matcherNode;
3036
3103
  matcherNameNode;
@@ -3072,12 +3139,51 @@ class AbilityLayer {
3072
3139
  #nodes = [];
3073
3140
  #projectService;
3074
3141
  #resolvedConfig;
3142
+ #suppressedErrorsMap;
3075
3143
  #text = "";
3076
3144
  constructor(compiler, projectService, resolvedConfig) {
3077
3145
  this.#compiler = compiler;
3078
3146
  this.#projectService = projectService;
3079
3147
  this.#resolvedConfig = resolvedConfig;
3080
3148
  }
3149
+ #addRanges(node, ranges) {
3150
+ this.#nodes.push(node);
3151
+ for (const range of ranges) {
3152
+ const rangeText = range.replacement != null
3153
+ ? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
3154
+ : this.#getErasedRangeText(range);
3155
+ this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
3156
+ }
3157
+ }
3158
+ #belongsToNode(diagnostic) {
3159
+ for (const node of this.#nodes) {
3160
+ if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
3161
+ node.abilityDiagnostics.add(diagnostic);
3162
+ return true;
3163
+ }
3164
+ }
3165
+ return false;
3166
+ }
3167
+ #belongsToDirective(diagnostic) {
3168
+ if (!isDiagnosticWithLocation(diagnostic)) {
3169
+ return;
3170
+ }
3171
+ const { file, start } = diagnostic;
3172
+ const lineMap = file.getLineStarts();
3173
+ let line = this.#compiler.getLineAndCharacterOfPosition(file, start).line - 1;
3174
+ while (line >= 0) {
3175
+ const suppressedError = this.#suppressedErrorsMap?.get(line);
3176
+ if (suppressedError != null) {
3177
+ suppressedError.diagnostics.push(diagnostic);
3178
+ break;
3179
+ }
3180
+ const lineText = file.text.slice(lineMap[line], lineMap[line + 1]).trim();
3181
+ if (lineText !== "" && !lineText.startsWith("//")) {
3182
+ break;
3183
+ }
3184
+ line--;
3185
+ }
3186
+ }
3081
3187
  #collectSuppressedErrors() {
3082
3188
  const ranges = [];
3083
3189
  for (const match of this.#text.matchAll(this.#expectErrorRegex)) {
@@ -3086,12 +3192,13 @@ class AbilityLayer {
3086
3192
  const ignoreText = match?.[3];
3087
3193
  const argumentSeparatorText = match?.[4];
3088
3194
  const argumentText = match?.[5]?.split(/--+/)[0]?.trimEnd();
3089
- if (typeof offsetText !== "string" || !directiveText || ignoreText === "!") {
3195
+ if (typeof offsetText !== "string" || !directiveText) {
3090
3196
  continue;
3091
3197
  }
3092
3198
  const start = match.index + offsetText.length;
3093
3199
  const range = {
3094
3200
  directive: { start, end: start + directiveText.length, text: directiveText },
3201
+ ignore: ignoreText === "!",
3095
3202
  diagnostics: [],
3096
3203
  };
3097
3204
  if (typeof argumentSeparatorText === "string" && typeof argumentText === "string") {
@@ -3102,52 +3209,24 @@ class AbilityLayer {
3102
3209
  }
3103
3210
  return ranges;
3104
3211
  }
3105
- #getErasedRangeText(range) {
3106
- if (this.#text.indexOf("\n", range.start) >= range.end) {
3107
- return " ".repeat(range.end - range.start);
3108
- }
3109
- const text = [];
3110
- for (let index = range.start; index < range.end; index++) {
3111
- const character = this.#text.charAt(index);
3112
- switch (character) {
3113
- case "\n":
3114
- case "\r":
3115
- text.push(character);
3116
- break;
3117
- default:
3118
- text.push(" ");
3119
- }
3120
- }
3121
- return text.join("");
3122
- }
3123
- #addRanges(node, ranges) {
3124
- this.#nodes.push(node);
3125
- for (const range of ranges) {
3126
- const rangeText = range.replacement != null
3127
- ? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
3128
- : this.#getErasedRangeText(range);
3129
- this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
3130
- }
3131
- }
3132
3212
  close() {
3133
- if (this.#nodes.length > 0) {
3213
+ if (this.#nodes.length > 0 || this.#suppressedErrorsMap != null) {
3134
3214
  this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
3135
3215
  const languageService = this.#projectService.getLanguageService(this.#filePath);
3136
- const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
3137
- for (const node of this.#nodes.reverse()) {
3216
+ const diagnostics = languageService?.getSemanticDiagnostics(this.#filePath);
3217
+ if (diagnostics != null) {
3218
+ this.#nodes.reverse();
3138
3219
  for (const diagnostic of diagnostics) {
3139
- if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
3140
- if (!node.abilityDiagnostics) {
3141
- node.abilityDiagnostics = new Set();
3142
- }
3143
- node.abilityDiagnostics.add(diagnostic);
3144
- diagnostics.delete(diagnostic);
3220
+ if (this.#belongsToNode(diagnostic)) {
3221
+ continue;
3145
3222
  }
3223
+ this.#belongsToDirective(diagnostic);
3146
3224
  }
3147
3225
  }
3148
3226
  }
3149
3227
  this.#filePath = "";
3150
3228
  this.#nodes = [];
3229
+ this.#suppressedErrorsMap = undefined;
3151
3230
  this.#text = "";
3152
3231
  }
3153
3232
  #eraseTrailingComma(node, parent) {
@@ -3155,24 +3234,23 @@ class AbilityLayer {
3155
3234
  this.#addRanges(parent, [{ start: node.end - 1, end: node.end }]);
3156
3235
  }
3157
3236
  }
3158
- handleWhen(whenNode) {
3159
- const whenStart = whenNode.node.getStart();
3160
- const whenExpressionEnd = whenNode.node.expression.getEnd();
3161
- const whenEnd = whenNode.node.getEnd();
3162
- const actionNameEnd = whenNode.actionNameNode.getEnd();
3163
- switch (whenNode.actionNameNode.name.text) {
3164
- case "isCalledWith":
3165
- this.#eraseTrailingComma(whenNode.target, whenNode);
3166
- this.#addRanges(whenNode, [
3167
- {
3168
- start: whenStart,
3169
- end: whenExpressionEnd,
3170
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3171
- },
3172
- { start: whenEnd, end: actionNameEnd },
3173
- ]);
3174
- break;
3237
+ #getErasedRangeText(range) {
3238
+ if (this.#text.indexOf("\n", range.start) >= range.end) {
3239
+ return " ".repeat(range.end - range.start);
3175
3240
  }
3241
+ const text = [];
3242
+ for (let index = range.start; index < range.end; index++) {
3243
+ const character = this.#text.charAt(index);
3244
+ switch (character) {
3245
+ case "\n":
3246
+ case "\r":
3247
+ text.push(character);
3248
+ break;
3249
+ default:
3250
+ text.push(" ");
3251
+ }
3252
+ }
3253
+ return text.join("");
3176
3254
  }
3177
3255
  handleAssertion(assertionNode) {
3178
3256
  const expectStart = assertionNode.node.getStart();
@@ -3210,18 +3288,45 @@ class AbilityLayer {
3210
3288
  break;
3211
3289
  }
3212
3290
  }
3213
- #handleSuppressedErrors() {
3291
+ #handleSuppressedErrors(testTree) {
3214
3292
  const suppressedErrors = this.#collectSuppressedErrors();
3293
+ if (this.#resolvedConfig.checkSuppressedErrors) {
3294
+ testTree.suppressedErrors = Object.assign(suppressedErrors, { sourceFile: testTree.sourceFile });
3295
+ this.#suppressedErrorsMap = new Map();
3296
+ }
3215
3297
  for (const suppressedError of suppressedErrors) {
3216
3298
  const { start, end } = suppressedError.directive;
3217
3299
  const rangeText = this.#getErasedRangeText({ start: start + 2, end });
3218
3300
  this.#text = `${this.#text.slice(0, start + 2)}${rangeText}${this.#text.slice(end)}`;
3301
+ if (this.#suppressedErrorsMap != null) {
3302
+ const { line } = testTree.sourceFile.getLineAndCharacterOfPosition(start);
3303
+ this.#suppressedErrorsMap.set(line, suppressedError);
3304
+ }
3219
3305
  }
3220
3306
  }
3221
- open(sourceFile) {
3222
- this.#filePath = sourceFile.fileName;
3223
- this.#text = sourceFile.text;
3224
- this.#handleSuppressedErrors();
3307
+ handleWhen(whenNode) {
3308
+ const whenStart = whenNode.node.getStart();
3309
+ const whenExpressionEnd = whenNode.node.expression.getEnd();
3310
+ const whenEnd = whenNode.node.getEnd();
3311
+ const actionNameEnd = whenNode.actionNameNode.getEnd();
3312
+ switch (whenNode.actionNameNode.name.text) {
3313
+ case "isCalledWith":
3314
+ this.#eraseTrailingComma(whenNode.target, whenNode);
3315
+ this.#addRanges(whenNode, [
3316
+ {
3317
+ start: whenStart,
3318
+ end: whenExpressionEnd,
3319
+ replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3320
+ },
3321
+ { start: whenEnd, end: actionNameEnd },
3322
+ ]);
3323
+ break;
3324
+ }
3325
+ }
3326
+ open(testTree) {
3327
+ this.#filePath = testTree.sourceFile.fileName;
3328
+ this.#text = testTree.sourceFile.text;
3329
+ this.#handleSuppressedErrors(testTree);
3225
3330
  }
3226
3331
  }
3227
3332
 
@@ -3348,6 +3453,7 @@ class TestTree {
3348
3453
  diagnostics;
3349
3454
  hasOnly = false;
3350
3455
  sourceFile;
3456
+ suppressedErrors;
3351
3457
  constructor(diagnostics, sourceFile) {
3352
3458
  this.diagnostics = diagnostics;
3353
3459
  this.sourceFile = sourceFile;
@@ -3360,7 +3466,7 @@ class TestTree {
3360
3466
  class WhenNode extends TestTreeNode {
3361
3467
  actionNode;
3362
3468
  actionNameNode;
3363
- abilityDiagnostics;
3469
+ abilityDiagnostics = new Set();
3364
3470
  target;
3365
3471
  constructor(compiler, brand, node, parent, flags, actionNode, actionNameNode) {
3366
3472
  super(compiler, brand, node, parent, flags);
@@ -3459,7 +3565,7 @@ class CollectService {
3459
3565
  createTestTree(sourceFile, semanticDiagnostics = []) {
3460
3566
  const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3461
3567
  EventEmitter.dispatch(["collect:start", { tree: testTree }]);
3462
- this.#abilityLayer.open(sourceFile);
3568
+ this.#abilityLayer.open(testTree);
3463
3569
  this.#identifierLookup.open();
3464
3570
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3465
3571
  this.#abilityLayer.close();
@@ -3596,6 +3702,9 @@ class ProjectService {
3596
3702
  defaultCompilerOptions.allowImportingTsExtensions = true;
3597
3703
  defaultCompilerOptions.verbatimModuleSyntax = true;
3598
3704
  }
3705
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.6")) {
3706
+ defaultCompilerOptions.noUncheckedSideEffectImports = true;
3707
+ }
3599
3708
  return defaultCompilerOptions;
3600
3709
  }
3601
3710
  getDefaultProject(filePath) {
@@ -3669,6 +3778,60 @@ class ProjectService {
3669
3778
  }
3670
3779
  }
3671
3780
 
3781
+ class SuppressedDiagnosticText {
3782
+ static directiveRequires() {
3783
+ return [
3784
+ "Directive requires an argument.",
3785
+ "Add a fragment of the expected error message after the directive.",
3786
+ "To ignore the directive, append the '!' character after it.",
3787
+ ];
3788
+ }
3789
+ static messageDidNotMatch() {
3790
+ return "The diagnostic message did not match.";
3791
+ }
3792
+ static onlySingleError() {
3793
+ return "Only a single error can be suppressed.";
3794
+ }
3795
+ static suppressedError(count = 1) {
3796
+ return `The suppressed error${count === 1 ? "" : "s"}:`;
3797
+ }
3798
+ }
3799
+
3800
+ class SuppressedService {
3801
+ match(suppressedErrors, onDiagnostics) {
3802
+ for (const suppressedError of suppressedErrors) {
3803
+ if (suppressedError.diagnostics.length === 0 || suppressedError.ignore) {
3804
+ continue;
3805
+ }
3806
+ if (!suppressedError.argument?.text) {
3807
+ const text = SuppressedDiagnosticText.directiveRequires();
3808
+ const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, suppressedErrors.sourceFile);
3809
+ onDiagnostics([Diagnostic.error(text, origin)]);
3810
+ continue;
3811
+ }
3812
+ const related = [
3813
+ Diagnostic.error(SuppressedDiagnosticText.suppressedError(suppressedError.diagnostics.length)),
3814
+ ...Diagnostic.fromDiagnostics(suppressedError.diagnostics, suppressedErrors.sourceFile),
3815
+ ];
3816
+ if (suppressedError.diagnostics.length > 1) {
3817
+ const text = [SuppressedDiagnosticText.onlySingleError()];
3818
+ const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, suppressedErrors.sourceFile);
3819
+ onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
3820
+ continue;
3821
+ }
3822
+ let messageText = getDiagnosticMessageText(suppressedError.diagnostics[0]);
3823
+ if (Array.isArray(messageText)) {
3824
+ messageText = messageText.join("\n");
3825
+ }
3826
+ if (!messageText.includes(suppressedError.argument.text)) {
3827
+ const text = [SuppressedDiagnosticText.messageDidNotMatch()];
3828
+ const origin = new DiagnosticOrigin(suppressedError.argument.start, suppressedError.argument.end, suppressedErrors.sourceFile);
3829
+ onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
3830
+ }
3831
+ }
3832
+ }
3833
+ }
3834
+
3672
3835
  var RunMode;
3673
3836
  (function (RunMode) {
3674
3837
  RunMode[RunMode["Normal"] = 0] = "Normal";
@@ -3853,7 +4016,9 @@ class MatchWorker {
3853
4016
  }
3854
4017
  }
3855
4018
  extendsObjectType(type) {
3856
- const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
4019
+ const nonPrimitiveType = "getNonPrimitiveType" in this.typeChecker
4020
+ ? this.typeChecker.getNonPrimitiveType()
4021
+ : { flags: this.#compiler.TypeFlags.NonPrimitive };
3857
4022
  return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
3858
4023
  }
3859
4024
  getParameterType(signature, index) {
@@ -4153,7 +4318,7 @@ class ToBeApplicable {
4153
4318
  #explain(matchWorker, sourceNode) {
4154
4319
  const targetText = this.#resolveTargetText(matchWorker.assertion.matcherNode.parent);
4155
4320
  const diagnostics = [];
4156
- if (matchWorker.assertion.abilityDiagnostics) {
4321
+ if (matchWorker.assertion.abilityDiagnostics.size > 0) {
4157
4322
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4158
4323
  const text = [ExpectDiagnosticText.cannotBeApplied(targetText), getDiagnosticMessageText(diagnostic)];
4159
4324
  const origin = DiagnosticOrigin.fromNode(sourceNode);
@@ -4179,7 +4344,7 @@ class ToBeApplicable {
4179
4344
  }
4180
4345
  return {
4181
4346
  explain: () => this.#explain(matchWorker, sourceNode),
4182
- isMatch: !matchWorker.assertion.abilityDiagnostics,
4347
+ isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4183
4348
  };
4184
4349
  }
4185
4350
  }
@@ -4224,7 +4389,7 @@ class AbilityMatcherBase {
4224
4389
  const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
4225
4390
  const targetText = this.#resolveTargetText(targetNodes);
4226
4391
  const diagnostics = [];
4227
- if (matchWorker.assertion.abilityDiagnostics) {
4392
+ if (matchWorker.assertion.abilityDiagnostics.size > 0) {
4228
4393
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4229
4394
  let origin;
4230
4395
  const text = [];
@@ -4289,7 +4454,7 @@ class ToBeCallableWith extends AbilityMatcherBase {
4289
4454
  }
4290
4455
  return {
4291
4456
  explain: () => this.explain(matchWorker, sourceNode, targetNodes),
4292
- isMatch: !matchWorker.assertion.abilityDiagnostics,
4457
+ isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4293
4458
  };
4294
4459
  }
4295
4460
  }
@@ -4324,7 +4489,7 @@ class ToBeConstructableWith extends AbilityMatcherBase {
4324
4489
  }
4325
4490
  return {
4326
4491
  explain: () => this.explain(matchWorker, sourceNode, targetNodes),
4327
- isMatch: !matchWorker.assertion.abilityDiagnostics,
4492
+ isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4328
4493
  };
4329
4494
  }
4330
4495
  }
@@ -4631,7 +4796,7 @@ class WhenService {
4631
4796
  this.#onActionIsNotSupported(actionNameText, when, this.#onDiagnostics);
4632
4797
  return;
4633
4798
  }
4634
- if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
4799
+ if (when.abilityDiagnostics.size > 0) {
4635
4800
  const diagnostics = [];
4636
4801
  for (const diagnostic of when.abilityDiagnostics) {
4637
4802
  if (isDiagnosticWithLocation(diagnostic)) {
@@ -4827,8 +4992,9 @@ class TestTreeWalker {
4827
4992
  class TaskRunner {
4828
4993
  #collectService;
4829
4994
  #compiler;
4830
- #resolvedConfig;
4831
4995
  #projectService;
4996
+ #resolvedConfig;
4997
+ #suppressedService = new SuppressedService();
4832
4998
  constructor(compiler, resolvedConfig) {
4833
4999
  this.#compiler = compiler;
4834
5000
  this.#resolvedConfig = resolvedConfig;
@@ -4869,6 +5035,11 @@ class TaskRunner {
4869
5035
  if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
4870
5036
  runMode |= RunMode.Skip;
4871
5037
  }
5038
+ if (testTree.suppressedErrors != null) {
5039
+ this.#suppressedService.match(testTree.suppressedErrors, (diagnostics) => {
5040
+ this.#onDiagnostics(diagnostics, taskResult);
5041
+ });
5042
+ }
4872
5043
  if (inlineConfig?.template) {
4873
5044
  if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
4874
5045
  this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
@@ -4913,7 +5084,7 @@ class TaskRunner {
4913
5084
  class Runner {
4914
5085
  #eventEmitter = new EventEmitter();
4915
5086
  #resolvedConfig;
4916
- static version = "4.0.2";
5087
+ static version = "4.2.0";
4917
5088
  constructor(resolvedConfig) {
4918
5089
  this.#resolvedConfig = resolvedConfig;
4919
5090
  }
@@ -5113,4 +5284,4 @@ class Cli {
5113
5284
  }
5114
5285
  }
5115
5286
 
5116
- 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 };
5287
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, Glob, 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.2",
3
+ "version": "4.2.0",
4
4
  "description": "Everything You Need for Type Testing.",
5
5
  "keywords": [
6
6
  "typescript",