import * as ts from 'typescript' ; /**
 * A Tsetse check Failure is almost identical to a Diagnostic from TypeScript
 * except that:
 * (1) The error code is defined by each individual Tsetse rule.
 * (2) The optional `source` property is set to `Tsetse` so the host (VS Code
 * for instance) would use that to indicate where the error comes from.
 * (3) There's an optional suggestedFix field.
 */ export class Failure { constructor ( private readonly sourceFile , private readonly start , private readonly end , private readonly failureText , private readonly code , private readonly suggestedFix ? ) { } /**
     * This returns a structure compatible with ts.Diagnostic, but with added
     * fields, for convenience and to support suggested fixes.
     */ toDiagnostic ( ) { return { file : this . sourceFile , start : this . start , end : this . end , length : this . end - this . start , messageText : this . failureText , category : ts . DiagnosticCategory . Error , code : this . code , source : 'Tsetse' , fix : this . suggestedFix } ; } /**
     * Same as toDiagnostic, but include the fix in the message, so that systems
     * that don't support displaying suggested fixes can still surface that
     * information. This assumes the diagnostic message is going to be presented
     * within the context of the problematic code
     */ toDiagnosticWithStringifiedFix ( ) { const diagnostic = this . toDiagnostic ( ) ; if ( this . suggestedFix ) { diagnostic . messageText += ' ' + this . fixToReadableStringInContext ( ) ; } return diagnostic ; } toString ( ) { return `{ sourceFile:${ this . sourceFile ? this . sourceFile . fileName : 'unknown' }, start:${ this . start }, end:${ this . end }, fix:${ fixToString ( this . suggestedFix ) } }` ; } /**
     * Stringifies a `Fix`, in a way that makes sense when presented alongside the
     * finding. This is a heuristic, obviously.
     */ fixToReadableStringInContext ( ) { if ( ! this . suggestedFix ) return '' ; const f = this . suggestedFix ; let fixText = '' ; for ( const c of f . changes ) { const printableReplacement = c . replacement . trim ( ) ; if ( c . start === c . end ) { if ( c . replacement . indexOf ( 'import' ) !== - 1 ) { fixText += `- Add new import: ${ printableReplacement }\n` ; } else { fixText += `- Insert ${ this . readableRange ( c . start , c . end ) }: ${ printableReplacement }\n` ; } } else if ( c . start === this . start && c . end === this . end ) { fixText = `- Replace the full match with: ${ printableReplacement }\n` + fixText ; } else { fixText = `- Replace ${ this . readableRange ( c . start , c . end ) } with: ` + `${ printableReplacement }\n${ fixText }` ; } } return 'Suggested fix:\n' + fixText . trim ( ) ; } readableRange ( from , to ) { const lcf = this . sourceFile . getLineAndCharacterOfPosition ( from ) ; const lct = this . sourceFile . getLineAndCharacterOfPosition ( to ) ; if ( lcf . line === lct . line ) { if ( lcf . character === lct . character ) { return `at line ${ lcf . line + 1 }, char ${ lcf . character + 1 }` ; } return `line ${ lcf . line + 1 }, from char ${ lcf . character + 1 } to ${ lct . character + 1 }` ; } else { return `from line ${ lcf . line + 1 }, char ${ lcf . character + 1 } to line ${ lct . line + 1 }, char ${ lct . character + 1 }` ; } } } /**
 * A `Fix` is a potential repair to the associated `Failure`.
 */ export interface Fix { /**
     * The individual text replacements composing that fix.
     */ changes : IndividualChange [ ] ; } /**
 * An individual text replacement/insertion in a source file. Used as part of a
 * `Fix`.
 */ export interface IndividualChange { sourceFile : ts . SourceFile ; start : number ; end : number ; replacement : string ; } /**
 * A ts.Diagnostic that might include a `Fix`, and with an added `end` field for
 * convenience.
 */ export interface DiagnosticWithFix extends ts . Diagnostic { end : number ; fix ? : Fix ; } /**
 * Stringifies a `Fix`, replacing the `ts.SourceFile` with the matching
 * filename.
 */ export function fixToString ( f ? ) { if ( ! f ) return 'undefined' ; return '{' + JSON . stringify ( f . changes . map ( ic => { return { start : ic . start , end : ic . end , replacement : ic . replacement , fileName : ic . sourceFile . fileName } ; } ) ) + '}' ; }