fileoverview import { ErrorCode } from './third_party/tsetse/error_code' ; import { DiagnosticWithFixes } from './third_party/tsetse/failure' ; import * as ts from 'typescript/lib/tsserverlibrary' ; import { getConfiguredChecker } from './build' ; import { createProxy } from './utils' ; function diagnosticToCodeFixActions ( d ) { const codeActions = [ ] ; for ( let i = 0 ; i < d . fixes ?. length || 0 ; i ++ ) { codeActions . push ( { fixName : 'Tsec fix' , description : `Apply tsec fix ${ i + 1 }` , changes : d . fixes [ i ] . changes . map ( c => ( { fileName : c . sourceFile . fileName , textChanges : [ { span : { start : c . start , length : c . end - c . start } , newText : c . replacement } ] } ) ) } ) ; } return codeActions ; } function computeKey ( start , end ) { return `[${ start },${ end }]` ; } interface CodeFix { errorCodes : number [ ] ; getCodeActions ( context ) : ts . CodeAction [ ] | undefined ; } interface TsWithCodefix { codefix : { registerCodeFix : ( codefix : CodeFix ) => void ; } ; } /**
 * Implementation of the tsec LSP.
 *
 * Instance methods of this class are used as references and they need to
 * bound `this`. This is the reason why we use arrow functions for the property
 * methods.
 */ class TsecLanguageServicePlugin { codeFixActions : Map < string , Map < string , ts . CodeFixAction [ ] > > ; oldService : ts . LanguageService ; constructor ( readonly info ) { this . codeFixActions = new Map ( ) ; this . oldService = info . languageService ; } boundGetSemanticDiagnostics = ( fileName ) => { const program = this . oldService . getProgram ( ) ; if ( ! program ) { throw new Error ( 'Failed to initialize tsetse language_service_plugin: program is undefined' ) ; } const { checker } = getConfiguredChecker ( program ) ; const failures = checker . execute ( program . getSourceFile ( fileName ) ! ) ; this . codeFixActions . set ( fileName , new Map ( ) ) ; const codeActionsForCurrentFile = this . codeFixActions . get ( fileName ) ! ; for ( const failure of failures ) { const d = failure . toDiagnostic ( ) ; const codeActions = diagnosticToCodeFixActions ( d ) ; if ( codeActions . length ) { const key = computeKey ( d . start ! , d . end ) ; if ( ! codeActionsForCurrentFile . has ( key ) ) { codeActionsForCurrentFile . set ( key , [ ] ) ; } codeActionsForCurrentFile . get ( key ) ! . push ( ... codeActions ) ; } } const result = this . oldService . getSemanticDiagnostics ( fileName ) ; result . push ( ... checker . execute ( program . getSourceFile ( fileName ) ! ) . map ( ( failure ) => failure . toDiagnostic ( ) ) ) ; return result ; } ; boundGetCodeFixesAtPosition = ( fileName , start , end , errorCodes , formatOptions , userPreferences ) : readonly ts . CodeFixAction [ ] => { const prior = this . oldService . getCodeFixesAtPosition ( fileName , start , end , errorCodes , formatOptions , userPreferences ) ; const fixes = [ ... prior ] ; const codeActionsForCurrentFile = this . codeFixActions . get ( fileName ) ; if ( codeActionsForCurrentFile ) { const actions = codeActionsForCurrentFile . get ( computeKey ( start , end ) ) ?? [ ] ; fixes . push ( ... actions ) ; } return fixes ; } ; } /**
 * Installs the Tsec language server plugin, which checks Tsec
 * rules in your editor and shows issues as semantic errors
 * (red squiggly underline).
 */ function init ( modules ) { const tsModule = modules . typescript ; if ( ( tsModule as unknown as TsWithCodefix ) . codefix ) { ( tsModule as unknown as TsWithCodefix ) . codefix . registerCodeFix ( { errorCodes : [ ErrorCode . CONFORMANCE_PATTERN ] , getCodeActions : ( ) => undefined } ) ; } return { create ( info ) { const plugin = new TsecLanguageServicePlugin ( info ) ; const oldService = info . languageService ; const proxy = createProxy ( oldService ) ; proxy . getSemanticDiagnostics = plugin . boundGetSemanticDiagnostics ; proxy . getCodeFixesAtPosition = plugin . boundGetCodeFixesAtPosition ; return proxy ; } } ; } export = init ;