Replace ranges with text matched in Tsetse matchers, and fix a type confusion

Both check that their expectations match the actual item. The filenames thing was due to me testing the expected value as an extended ts.Diagnostic, while never converting it. Jasmine isn't typed that way.

PiperOrigin-RevId: 249810909
diff --git a/internal/tsetse/failure.ts b/internal/tsetse/failure.ts
index 56355ad..20126ac 100644
--- a/internal/tsetse/failure.ts
+++ b/internal/tsetse/failure.ts
@@ -34,6 +34,12 @@
       fix: this.suggestedFix
     };
   }
+
+  toString(): string {
+    return `Failure{sourceFile:${
+        this.sourceFile ? this.sourceFile.fileName :
+                          'unknown'}, start:${this.start}, end:${this.end}}`;
+  }
 }
 
 /**
diff --git a/internal/tsetse/tests/ban_conformance_pattern/property_write_test.ts b/internal/tsetse/tests/ban_conformance_pattern/property_write_test.ts
index 45f0c3a..997577e 100644
--- a/internal/tsetse/tests/ban_conformance_pattern/property_write_test.ts
+++ b/internal/tsetse/tests/ban_conformance_pattern/property_write_test.ts
@@ -32,11 +32,9 @@
 
     expect(results.length).toBe(1);
     expect(results[0]).toBeFailureMatching({
-      start: 28,
-      end: 48,
+      matchedCode: 'q.cite = window.name;',
+      fileName: 'file_1.ts',
       errorMessage: 'do not cite',
-      // fileName: 'file_0.ts'
-      // TODO(rjamet): why is there no source file in the finding?
     });
   });
 
@@ -52,8 +50,7 @@
     // Both of these should have the same results: in `c.x`, `x` matches,
     // and `c` is both a Parent and a Child.
     const expectedFailure = {
-      start: 83,
-      end: 90,
+      matchedCode: 'c.x = 1;',
       errorMessage: 'found write to x',
     };
 
diff --git a/internal/tsetse/util/testing/test_support.ts b/internal/tsetse/util/testing/test_support.ts
index 8adb90a..f79192c 100644
--- a/internal/tsetse/util/testing/test_support.ts
+++ b/internal/tsetse/util/testing/test_support.ts
@@ -6,7 +6,7 @@
 import * as ts from 'typescript';
 
 import {Checker} from '../../checker';
-import {Failure, Fix} from '../../failure';
+import {Failure} from '../../failure';
 import {AbstractRule} from '../../rule';
 
 
@@ -70,26 +70,44 @@
 export const customMatchers: jasmine.CustomMatcherFactories = {
   toBeFailureMatching(): jasmine.CustomMatcher {
     return {
-      compare: (actual: ts.Diagnostic&{end: number, fix?: Fix}, exp: {
-        fileName?: string, start: number, end: number,
+      compare: (actualFailure: Failure, exp: {
+        fileName?: string,
+        start?: number,
+        end?: number,
+        matchedCode?: string
       }) => {
+        const actualDiagnostic = actualFailure.toDiagnostic();
         let regrets = '';
         if (exp === undefined) {
           regrets += 'The rule requires two arguments. ';
         }
         if (exp.fileName) {
-          if (!actual.file) {
-            regrets += 'Expected diagnostic to have a source file. ';
-          } else if (!actual.file.fileName.endsWith(exp.fileName)) {
-            regrets += `Expected ${actual.file.fileName} to end with ${
-                exp.fileName}. `;
+          if (!actualDiagnostic.file) {
+            regrets += `Expected diagnostic to have a source file, but it had ${
+                actualDiagnostic.file}. `;
+          } else if (!actualDiagnostic.file.fileName.endsWith(exp.fileName)) {
+            regrets += `Expected ${
+                actualDiagnostic.file.fileName} to end with ${exp.fileName}. `;
           }
         }
-        if (exp.start && actual.start !== exp.start) {
-          regrets += expectation('start', exp.start, actual.start);
+        if (exp.start && actualDiagnostic.start !== exp.start) {
+          regrets += expectation('start', exp.start, actualDiagnostic.start);
         }
-        if (exp.end && actual.end !== exp.end) {
-          regrets += expectation('end', exp.end, actual.end);
+        if (exp.end && actualDiagnostic.end !== exp.end) {
+          regrets += expectation('end', exp.end, actualDiagnostic.end);
+        }
+        if (exp.matchedCode) {
+          if (!actualDiagnostic.file) {
+            regrets += `Expected diagnostic to have a source file, but it had ${
+                actualDiagnostic.file}. `;
+          } else {
+            const foundMatchedCode = actualDiagnostic.file.getFullText().substr(
+                Number(actualDiagnostic.start), actualDiagnostic.end);
+            if (foundMatchedCode != exp.matchedCode) {
+              regrets += `Expected diagnostic to match ${
+                  exp.matchedCode}, but was ${foundMatchedCode}`;
+            }
+          }
         }
         return {pass: regrets === '', message: regrets};
       }
@@ -106,10 +124,11 @@
   namespace jasmine {
     interface Matchers<T> {
       toBeFailureMatching(expected: {
+        [i: string]: any,  // the rest
         fileName?: string,
-                start: number,
-                end: number,
-                [i: string]: any  // the rest
+        start?: number,
+        end?: number,
+        matchedCode?: string,
       }): void;
     }
   }