'use strict' ; import * as minimatch from 'minimatch' ; import * as server from 'vscode-languageserver' ; import { ConfigurationRequest } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed' ; import * as fs from 'fs' ; import * as path from 'path' ; import * as semver from 'semver' ; import Uri from 'vscode-uri' ; import * as util from 'util' ; import * as tslint from 'tslint' ; import { Delayer } from './delayer' ; import { createVscFixForRuleFailure , TSLintAutofixEdit } from './fixer' ; import * as vueParser from 'vue-parser' ; import * as ts from 'typescript' ; interface Settings { enable : boolean ; jsEnable : boolean ; rulesDirectory : string | string [ ] ; configFile : string ; ignoreDefinitionFiles : boolean ; exclude : string | string [ ] ; validateWithDefaultConfig : boolean ; nodePath : string | undefined ; run : 'onSave' | 'onType' ; alwaysShowRuleFailuresAsWarnings : boolean ; autoFixOnSave : boolean | string [ ] ; trace : any ; typeCheck : boolean ; } interface Configuration { linterConfiguration : tslint . Configuration . IConfigurationFile | undefined ; isDefaultLinterConfig : boolean ; } class ConfigCache { filePath : string | undefined ; configuration : Configuration | undefined ; constructor ( ) { this . filePath = undefined ; this . configuration = undefined ; } set ( path , configuration ) { this . filePath = path ; this . configuration = configuration ; } get ( forPath ) { if ( forPath === this . filePath ) { return this . configuration ; } return undefined ; } isDefaultLinterConfig ( ) { if ( this . configuration ) { return this . configuration . isDefaultLinterConfig ; } return false ; } flush ( ) { this . filePath = undefined ; this . configuration = undefined ; } } class SettingsCache { uri : string | undefined ; settings : Settings | undefined ; constructor ( ) { this . uri = undefined ; this . settings = undefined ; } async get ( uri ) { if ( uri === this . uri ) { return this . settings ; } if ( scopedSettingsSupport ) { let configRequestParam = { items : [ { scopeUri : uri , section : 'tslint' } ] } ; let settings = await connection . sendRequest ( ConfigurationRequest . type , configRequestParam ) ; this . settings = settings [ 0 ] ; this . uri = uri ; return this . settings ; } return globalSettings ; } flush ( ) { this . uri = undefined ; this . settings = undefined ; } } let configCache = new ConfigCache ( ) ; let settingsCache = new SettingsCache ( ) ; let globalSettings = < Settings > { } ; let scopedSettingsSupport = false ; class ID { private static base : string = `${ Date . now ( ) . toString ( ) }-` ; private static counter : number = 0 ; public static next ( ) { return `${ ID . base }${ ID . counter ++ }` ; } } function computeKey ( diagnostic ) { let range = diagnostic . range ; return `[${ range . start . line },${ range . start . character },${ range . end . line },${ range . end . character }]-${ diagnostic . code }` ; } export interface AutoFix { label : string ; documentVersion : number ; problem : tslint . RuleFailure ; edits : TSLintAutofixEdit [ ] ; } enum Status { ok = 1 , warn = 2 , error = 3 } interface StatusParams { state : Status ; } namespace StatusNotification { export const type = new server . NotificationType < StatusParams , void > ( 'tslint/status' ) ; } interface NoTSLintLibraryParams { source : server . TextDocumentIdentifier ; } interface NoTSLintLibraryResult { } namespace NoTSLintLibraryRequest { export const type = new server . RequestType < NoTSLintLibraryParams , NoTSLintLibraryResult , void , void > ( 'tslint/noLibrary' ) ; } let globalNodePath = undefined ; let path2Library = new Map ( ) ; let document2Library = new Map ( ) ; let validationDelayer = new Map < string , Delayer < void > > ( ) ; let tslintNotFound = `Failed to load tslint library. Please install tslint in your workspace
folder using \'npm install tslint\' or \'npm install -g tslint\' and then press Retry.` ; let tslintNotFoundIgnored = `[vscode-tslint] Failed to load tslint library. This failure is not reported to the user since there is no \'tslint.json\' in the workspace` ; let configFileWatchers = new Map ( ) ; function makeDiagnostic ( settings , problem ) { let message = ( problem . getRuleName ( ) ) ? `${ problem . getFailure ( ) } (${ problem . getRuleName ( ) })` : `${ problem . getFailure ( ) }` ; let severity ; let alwaysWarning = settings && settings . alwaysShowRuleFailuresAsWarnings ; if ( ! alwaysWarning && problem . getRuleSeverity && problem . getRuleSeverity ( ) === 'error' ) { severity = server . DiagnosticSeverity . Error ; } else { severity = server . DiagnosticSeverity . Warning ; } let diagnostic = { severity : severity , message : message , range : { start : { line : problem . getStartPosition ( ) . getLineAndCharacter ( ) . line , character : problem . getStartPosition ( ) . getLineAndCharacter ( ) . character } , end : { line : problem . getEndPosition ( ) . getLineAndCharacter ( ) . line , character : problem . getEndPosition ( ) . getLineAndCharacter ( ) . character } , } , code : problem . getRuleName ( ) , source : 'tslint' } ; return diagnostic ; } let codeFixActions = new Map < string , Map < string , tslint . RuleFailure > > ( ) ; let codeDisableRuleActions = new Map < string , Map < string , tslint . RuleFailure > > ( ) ; function recordCodeAction ( document , diagnostic , problem ) { let documentDisableRuleFixes = codeDisableRuleActions [ document . uri ] ; if ( ! documentDisableRuleFixes ) { documentDisableRuleFixes = Object . create ( null ) ; codeDisableRuleActions [ document . uri ] = documentDisableRuleFixes ; } documentDisableRuleFixes [ computeKey ( diagnostic ) ] = createDisableRuleFix ( problem , document ) ; let fix = undefined ; if ( problem . getFix && problem . getFix ( ) && ! replacementsAreEmpty ( problem . getFix ( ) ) ) { fix = createAutoFix ( problem , document , problem . getFix ( ) ! ) ; } if ( ! fix ) { let vscFix = createVscFixForRuleFailure ( problem , document ) ; if ( vscFix ) { fix = createAutoFix ( problem , document , vscFix ) ; } } if ( ! fix ) { return ; } let documentAutoFixes = codeFixActions [ document . uri ] ; if ( ! documentAutoFixes ) { documentAutoFixes = Object . create ( null ) ; codeFixActions [ document . uri ] = documentAutoFixes ; } documentAutoFixes [ computeKey ( diagnostic ) ] = fix ; } function convertReplacementToAutoFix ( document , repl ) { let start = document . positionAt ( repl . start ) ; let end = document . positionAt ( repl . end ) ; return { range : [ start , end ] , text : repl . text , } ; } async function getConfiguration ( uri , filePath , library , configFileName ) { let config = configCache . get ( filePath ) ; if ( config ) { return config ; } let isDefaultConfig = false ; let linterConfiguration ; let linter = getLinterFromLibrary ( library ) ; if ( isTsLintVersion4 ( library ) ) { if ( linter . findConfigurationPath ) { isDefaultConfig = linter . findConfigurationPath ( configFileName , filePath ) === undefined ; } let configurationResult = linter . findConfiguration ( configFileName , filePath ) ; if ( ( < any > configurationResult ) . error ) { throw ( < any > configurationResult ) . error ; } linterConfiguration = configurationResult . results ; } else { if ( linter . findConfigurationPath ) { isDefaultConfig = linter . findConfigurationPath ( configFileName , filePath ) === undefined ; } linterConfiguration = < tslint . Configuration . IConfigurationFile > linter . findConfiguration ( configFileName , filePath ) ; } let configuration = { isDefaultLinterConfig : isDefaultConfig , linterConfiguration : linterConfiguration , } ; configCache . set ( filePath , configuration ) ; return configCache . configuration ; } function getErrorMessage ( err , document ) { let errorMessage = `unknown error` ; if ( typeof err . message === 'string' || err . message instanceof String ) { errorMessage = < string > err . message ; } let fsPath = server . Files . uriToFilePath ( document . uri ) ; let message = `vscode-tslint: '${ errorMessage }' while validating: ${ fsPath } stacktrace: ${ err . stack }` ; return message ; } function getConfigurationFailureMessage ( err ) { let errorMessage = `unknown error` ; if ( typeof err . message === 'string' || err . message instanceof String ) { errorMessage = < string > err . message ; } return `vscode-tslint: Cannot read tslint configuration - '${ errorMessage }'` ; } function showConfigurationFailure ( conn , err ) { conn . console . info ( getConfigurationFailureMessage ( err ) ) ; conn . sendNotification ( StatusNotification . type , { state : Status . error } ) ; } function validateAllTextDocuments ( conn , documents ) { let tracker = new server . ErrorMessageTracker ( ) ; documents . forEach ( document => { try { validateTextDocument ( conn , document ) ; } catch ( err ) { tracker . add ( getErrorMessage ( err , document ) ) ; } } ) ; } function getLinterFromLibrary ( library ) { let isTsLint4 = isTsLintVersion4 ( library ) ; let linter ; if ( ! isTsLint4 ) { linter = library ; } else { linter = library . Linter ; } return linter ; } async function validateTextDocument ( connection , document ) { let uri = document . uri ; let settings = await settingsCache . get ( uri ) ; if ( settings && ! settings . enable ) { return ; } if ( ! document2Library . has ( document . uri ) ) { await loadLibrary ( document . uri ) ; } if ( ! document2Library . has ( document . uri ) ) { return ; } document2Library . get ( document . uri ) ! . then ( async ( library ) => { if ( ! library ) { return ; } try { let diagnostics = await doValidate ( connection , library , document ) ; connection . sendDiagnostics ( { uri , diagnostics } ) ; } catch ( err ) { connection . window . showErrorMessage ( getErrorMessage ( err , document ) ) ; } } ) ; } let connection = server . createConnection ( new server . IPCMessageReader ( process ) , new server . IPCMessageWriter ( process ) ) ; let documents = new server . TextDocuments ( ) ; let workspacePath ; documents . listen ( connection ) ; function trace ( message , verbose ? ) { connection . tracer . log ( message , verbose ) ; } connection . onInitialize ( ( params ) => { workspacePath = params . rootPath ; function hasClientCapability ( name ) { let keys = name . split ( '.' ) ; let c = params . capabilities ; for ( let i = 0 ; c && i < keys . length ; i ++ ) { c = c [ keys [ i ] ] ; } return ! ! c ; } scopedSettingsSupport = hasClientCapability ( 'workspace.configuration' ) ; globalNodePath = server . Files . resolveGlobalNodePath ( ) ; return { capabilities : { textDocumentSync : documents . syncKind , codeActionProvider : true } } ; } ) ; function isTsLintVersion4 ( library ) { let version = '1.0.0' ; try { version = library . Linter . VERSION ; } catch ( e ) { } return ! ( semver . satisfies ( version , "<= 3.x.x" ) ) ; } async function loadLibrary ( docUri ) { let uri = Uri . parse ( docUri ) ; let promise ; let settings = await settingsCache . get ( docUri ) ; if ( uri . scheme === 'file' ) { let file = uri . fsPath ; let directory = path . dirname ( file ) ; if ( settings && settings . nodePath ) { promise = server . Files . resolve ( 'tslint' , settings . nodePath , settings . nodePath ! , trace ) . then < string , string > ( undefined , ( ) => { return server . Files . resolve ( 'tslint' , globalNodePath , directory , trace ) ; } ) ; } else { promise = server . Files . resolve ( 'tslint' , globalNodePath , directory , trace ) ; } } else { promise = server . Files . resolve ( 'tslint' , globalNodePath , undefined ! , trace ) ; } document2Library . set ( docUri , promise . then ( ( path ) => { let library ; if ( ! path2Library . has ( path ) ) { library = require ( path ) ; connection . console . info ( `TSLint library loaded from: ${ path }` ) ; path2Library . set ( path , library ) ; } return path2Library . get ( path ) ; } , ( ) => { connection . sendRequest ( NoTSLintLibraryRequest . type , { source : { uri : docUri } } ) ; return undefined ; } ) ) ; } let program = undefined ; async function doValidate ( conn , library , document ) { let uri = document . uri ; let diagnostics = [ ] ; delete codeFixActions [ uri ] ; delete codeDisableRuleActions [ uri ] ; let fsPath = server . Files . uriToFilePath ( uri ) ; if ( ! fsPath ) { return diagnostics ; } let settings = await settingsCache . get ( uri ) ; if ( ! settings ) { return diagnostics ; } if ( fileIsExcluded ( settings , fsPath ) ) { return diagnostics ; } let contents = document . getText ( ) ; if ( document . languageId === 'vue' ) contents = vueParser . parse ( contents , 'script' , { lang : [ 'ts' , 'tsx' , 'js' , 'jsx' ] } ) ; let configFile = settings . configFile || null ; let configuration ; try { configuration = await getConfiguration ( uri , fsPath , library , configFile ) ; } catch ( err ) { showConfigurationFailure ( conn , err ) ; return diagnostics ; } if ( ! configuration ) { return diagnostics ; } if ( isJsDocument ( document ) && ! settings . jsEnable ) { return diagnostics ; } if ( ! configCache . configuration ) { return diagnostics ; } if ( settings . validateWithDefaultConfig === false && configCache . configuration . isDefaultLinterConfig ) { return diagnostics ; } if ( configCache . configuration . isDefaultLinterConfig && settings . validateWithDefaultConfig === false ) { return diagnostics ; } let result ; let options = { formatter : "json" , fix : false , rulesDirectory : settings . rulesDirectory || undefined , formattersDirectory : undefined } ; if ( settings . trace && settings . trace . server === 'verbose' ) { traceConfigurationFile ( configuration . linterConfiguration ) ; } try { let linter = getLinterFromLibrary ( library ) ; if ( isTsLintVersion4 ( library ) ) { let tslint ; if ( settings . typeCheck ) { program = createProgram ( document . uri , contents , program ) ; tslint = new linter ( options , program ) ; } else { tslint = new linter ( options ) ; } tslint . lint ( fsPath , contents , configuration . linterConfiguration ) ; result = tslint . getResult ( ) ; } else if ( ! isJsDocument ( document ) ) { ( < any > options ) . configuration = configuration ; let tslint = new ( < any > linter ) ( fsPath , contents , options ) ; result = tslint . lint ( ) ; } else { return diagnostics ; } } catch ( err ) { conn . console . info ( getErrorMessage ( err , document ) ) ; connection . sendNotification ( StatusNotification . type , { state : Status . error } ) ; return diagnostics ; } if ( result . failures . length > 0 ) { filterProblemsForDocument ( fsPath , result . failures ) . forEach ( problem => { let diagnostic = makeDiagnostic ( settings , problem ) ; diagnostics . push ( diagnostic ) ; recordCodeAction ( document , diagnostic , problem ) ; } ) ; } connection . sendNotification ( StatusNotification . type , { state : Status . ok } ) ; return diagnostics ; } /**
 * Create a program so linting can work with typechecking.
 */ function createProgram ( updatedFileName , updatedContents , oldProgram ? ) { const parsed = getParsedTsConfig ( ) ; const host = ts . createCompilerHost ( parsed . options , true ) ; const realGetSourceFile = host . getSourceFile ; updatedFileName = fixSlashes ( updatedFileName ) ; host . getSourceFile = function getSourceFile ( fileName , languageVersion , onError ) { if ( updatedFileName && updatedFileName . indexOf ( fixSlashes ( encodePath ( fileName ) ) ) !== - 1 ) { return ts . createSourceFile ( fileName , updatedContents , languageVersion , true ) ; } else if ( isVue ( fileName ) ) { const sourceText = host . readFile ( fileName ) || '' ; const parsed = vueParser . parse ( sourceText , 'script' , { lang : [ 'ts' , 'tsx' , 'js' , 'jsx' ] } ) ; return ts . createSourceFile ( fileName , parsed , languageVersion , true ) ; } else { return realGetSourceFile ( fileName , languageVersion , onError ) ; } } ; host . resolveModuleNames = function ( moduleNames , containingFile ) { const resolvedModules = [ ] ; for ( let moduleName of moduleNames ) { let result = ts . resolveModuleName ( moduleName , containingFile , parsed . options , host ) ; if ( result . resolvedModule ) { resolvedModules . push ( result . resolvedModule ) ; } else { const absolutePath = resolveNonTsModuleName ( moduleName , containingFile ) ; if ( isVue ( moduleName ) ) { resolvedModules . push ( { resolvedFileName : absolutePath , extension : '.ts' } as ts . ResolvedModuleFull ) ; } else { resolvedModules . push ( { resolvedFileName : host . fileExists ( absolutePath ) ? '' : absolutePath , extension : '.ts' } as ts . ResolvedModuleFull ) ; } } } return resolvedModules ; } ; return ts . createProgram ( parsed . fileNames , parsed . options , host , oldProgram ) ; } /**
 * Normalizes slashes (win32 vs. Posix).
 */ function fixSlashes ( path ) { return path . replace ( /\\/g , "/" ) ; } /**
 * Encode path (and fix semicolon issue with Windows paths).
 */ function encodePath ( path ) { return encodeURI ( path ) . replace ( /:/g , "%3A" ) ; } /**
 * Fixes a non .ts module's name so the file and source text can be properly found later.
 * Assumes wildcard "@" is [workspace root]/src.
 */ function resolveNonTsModuleName ( moduleName , containingFile ) { if ( moduleName . indexOf ( '@/' ) === 0 ) { moduleName = path . resolve ( workspacePath || '' , 'src' + moduleName . substr ( 1 ) ) ; } else if ( moduleName . indexOf ( './' ) === 0 || moduleName . indexOf ( '../' ) === 0 ) { moduleName = path . resolve ( path . dirname ( containingFile ) , moduleName ) ; } return moduleName ; } /**
 * Determines if file is a Vue file.
 */ function isVue ( filePath ) { return path . extname ( filePath ) === '.vue' ; } /**
 * Get the tsconfig.json file and parse it into an object.
 */ function getParsedTsConfig ( ) { const configFile = ts . findConfigFile ( workspacePath || '' , ts . sys . fileExists ) ; const extraExtensions = [ 'vue' ] ; const parseConfigHost = { fileExists : ts . sys . fileExists , readFile : ts . sys . readFile , useCaseSensitiveFileNames : ts . sys . useCaseSensitiveFileNames , readDirectory : ( rootDir , extensions , excludes , includes , depth ) => { return ts . sys . readDirectory ( rootDir , extensions . concat ( extraExtensions ) , excludes , includes , depth ) ; } } ; const parsed = ts . parseJsonConfigFileContent ( ts . readConfigFile ( configFile , ts . sys . readFile ) . config , parseConfigHost , path . dirname ( configFile ) , { noEmit : true } ) ; parsed . options . allowNonTsExtensions = true ; return parsed ; } /**
 * Filter failures for the given document
 */ function filterProblemsForDocument ( documentPath , failures ) { let normalizedPath = path . normalize ( documentPath ) ; let normalizedFiles = { } ; return failures . filter ( each => { let fileName = each . getFileName ( ) ; if ( ! normalizedFiles [ fileName ] ) { normalizedFiles [ fileName ] = path . normalize ( fileName ) ; } return normalizedFiles [ fileName ] === normalizedPath ; } ) ; } function isJsDocument ( document ) { return ( document . languageId === "javascript" || document . languageId === "javascriptreact" ) ; } function fileIsExcluded ( settings , path ) { function testForExclusionPattern ( path , pattern ) { return minimatch ( path , pattern ) ; } if ( settings . ignoreDefinitionFiles ) { if ( minimatch ( path , "**/*.d.ts" ) ) { return true ; } } if ( settings . exclude ) { if ( Array . isArray ( settings . exclude ) ) { for ( let pattern of settings . exclude ) { if ( testForExclusionPattern ( path , pattern ) ) { return true ; } } } else if ( testForExclusionPattern ( path , < string > settings . exclude ) ) { return true ; } } return false ; } documents . onDidChangeContent ( async ( event ) => { let settings = await settingsCache . get ( event . document . uri ) ; if ( settings && settings . run === 'onType' ) { triggerValidateDocument ( event . document ) ; } } ) ; documents . onDidSave ( async ( event ) => { let settings = await settingsCache . get ( event . document . uri ) ; if ( settings && settings . run === 'onSave' ) { triggerValidateDocument ( event . document ) ; } } ) ; documents . onDidClose ( ( event ) => { connection . sendDiagnostics ( { uri : event . document . uri , diagnostics : [ ] } ) ; document2Library . delete ( event . document . uri ) ; } ) ; function triggerValidateDocument ( document ) { let d = validationDelayer [ document . uri ] ; if ( ! d ) { d = new Delayer < void > ( 200 ) ; validationDelayer [ document . uri ] = d ; } d . trigger ( ( ) => { validateTextDocument ( connection , document ) ; delete validationDelayer [ document . uri ] ; } ) ; } function tslintConfigurationValid ( ) { try { documents . all ( ) . forEach ( ( each ) => { let fsPath = server . Files . uriToFilePath ( each . uri ) ; if ( fsPath ) { } } ) ; } catch ( err ) { connection . console . info ( getConfigurationFailureMessage ( err ) ) ; connection . sendNotification ( StatusNotification . type , { state : Status . error } ) ; return false ; } return true ; } connection . onDidChangeConfiguration ( ( params ) => { globalSettings = params . settings ; configCache . flush ( ) ; settingsCache . flush ( ) ; validateAllTextDocuments ( connection , documents . all ( ) ) ; } ) ; connection . onDidChangeWatchedFiles ( ( params ) => { params . changes . forEach ( element => { let configFilePath = server . Files . uriToFilePath ( element . uri ) ; if ( configFilePath ) { let cached = require . cache [ configFilePath ] ; if ( cached ) { delete require . cache [ configFilePath ] ; } } } ) ; configCache . flush ( ) ; if ( tslintConfigurationValid ( ) ) { validateAllTextDocuments ( connection , documents . all ( ) ) ; } } ) ; connection . onCodeAction ( ( params ) => { let result = [ ] ; let uri = params . textDocument . uri ; let documentVersion = - 1 ; let ruleId = undefined ; let documentFixes = codeFixActions [ uri ] ; if ( documentFixes ) { for ( let diagnostic of params . context . diagnostics ) { let autoFix = documentFixes [ computeKey ( diagnostic ) ] ; if ( autoFix ) { documentVersion = autoFix . documentVersion ; ruleId = autoFix . problem . getRuleName ( ) ; result . push ( server . Command . create ( autoFix . label , '_tslint.applySingleFix' , uri , documentVersion , createTextEdit ( autoFix ) ) ) ; } } if ( result . length > 0 ) { let same = [ ] ; let all = [ ] ; let fixes = Object . keys ( documentFixes ) . map ( key => documentFixes [ key ] ) ; fixes = sortFixes ( fixes ) ; for ( let autofix of fixes ) { if ( documentVersion === - 1 ) { documentVersion = autofix . documentVersion ; } if ( autofix . problem . getRuleName ( ) === ruleId && ! overlaps ( getLastEdit ( same ) , autofix ) ) { same . push ( autofix ) ; } if ( ! overlaps ( getLastEdit ( all ) , autofix ) ) { all . push ( autofix ) ; } } if ( same . length > 1 ) { result . push ( server . Command . create ( `Fix all: ${ same [ 0 ] . problem . getFailure ( ) }` , '_tslint.applySameFixes' , uri , documentVersion , concatenateEdits ( same ) ) ) ; } if ( all . length > 1 ) { result . push ( server . Command . create ( `Fix all auto-fixable problems` , '_tslint.applyAllFixes' , uri , documentVersion , concatenateEdits ( all ) ) ) ; } } } let disableRuleFixes = codeDisableRuleActions [ uri ] ; if ( disableRuleFixes ) { for ( let diagnostic of params . context . diagnostics ) { let autoFix = disableRuleFixes [ computeKey ( diagnostic ) ] ; if ( autoFix ) { documentVersion = autoFix . documentVersion ; ruleId = autoFix . problem . getRuleName ( ) ; result . push ( server . Command . create ( autoFix . label , '_tslint.applyDisableRule' , uri , documentVersion , createTextEdit ( autoFix ) ) ) ; } } } if ( documentFixes ) { for ( let diagnostic of params . context . diagnostics ) { let autoFix = disableRuleFixes [ computeKey ( diagnostic ) ] ; if ( autoFix ) { documentVersion = autoFix . documentVersion ; let ruleId = autoFix . problem . getRuleName ( ) ; result . push ( server . Command . create ( `Show documentation for "${ ruleId }"` , '_tslint.showRuleDocumentation' , uri , documentVersion , undefined , ruleId ) ) ; } } } return result ; } ) ; function replacementsAreEmpty ( fix ) { if ( ( < any > fix ) . replacements ) { return ( < any > fix ) . replacements . length === 0 ; } if ( Array . isArray ( fix ) ) { return fix . length === 0 ; } return false ; } function createAutoFix ( problem , document , fix ) { let edits = [ ] ; function isTslintAutofixEdit ( fix ) { return ( < TSLintAutofixEdit > fix ) . range !== undefined ; } if ( isTslintAutofixEdit ( fix ) ) { edits = [ fix ] ; } else { let ff = fix ; if ( ff . replacements ) { edits = ff . replacements . map ( each => convertReplacementToAutoFix ( document , each ) ) ; } else { if ( ! Array . isArray ( fix ) ) { fix = [ fix ] ; } edits = fix . map ( each => convertReplacementToAutoFix ( document , each ) ) ; } } let autofix = { label : `Fix: ${ problem . getFailure ( ) }` , documentVersion : document . version , problem : problem , edits : edits , } ; return autofix ; } function createDisableRuleFix ( problem , document ) { let pos = { character : 0 , line : problem . getStartPosition ( ) . getLineAndCharacter ( ) . line } ; let disableEdit = { range : [ pos , pos ] , text : `// tslint:disable-next-line:${ problem . getRuleName ( ) }\n` } ; let disableFix = { label : `Disable rule "${ problem . getRuleName ( ) }"` , documentVersion : document . version , problem : problem , edits : [ disableEdit ] } ; return disableFix ; } function sortFixes ( fixes ) { return fixes . sort ( ( a , b ) => { let editA = a . edits [ 0 ] ; let editB = b . edits [ 0 ] ; if ( editA . range [ 0 ] < editB . range [ 0 ] ) { return - 1 ; } if ( editA . range [ 0 ] > editB . range [ 0 ] ) { return 1 ; } if ( editA . range [ 1 ] < editB . range [ 1 ] ) { return - 1 ; } if ( editA . range [ 1 ] > editB . range [ 1 ] ) { return 1 ; } return 0 ; } ) ; } export function overlaps ( lastFix , nextFix ) { if ( ! lastFix ) { return false ; } let doesOverlap = false ; lastFix . edits . some ( last => { return nextFix . edits . some ( next => { if ( last . range [ 1 ] . line > next . range [ 0 ] . line ) { doesOverlap = true ; return true ; } else if ( last . range [ 1 ] . line < next . range [ 0 ] . line ) { return false ; } else if ( last . range [ 1 ] . character >= next . range [ 0 ] . character ) { doesOverlap = true ; return true ; } return false ; } ) ; } ) ; return doesOverlap ; } function getLastEdit ( array ) { let length = array . length ; if ( length === 0 ) { return undefined ; } return array [ length - 1 ] ; } export function getAllNonOverlappingFixes ( fixes ) { let nonOverlapping = [ ] ; fixes = sortFixes ( fixes ) ; for ( let autofix of fixes ) { if ( ! overlaps ( getLastEdit ( nonOverlapping ) , autofix ) ) { nonOverlapping . push ( autofix ) ; } } return nonOverlapping ; } function createTextEdit ( autoFix ) { return autoFix . edits . map ( each => server . TextEdit . replace ( server . Range . create ( each . range [ 0 ] , each . range [ 1 ] ) , each . text || '' ) ) ; } interface AllFixesParams { textDocument : server . TextDocumentIdentifier ; isOnSave : boolean ; } interface AllFixesResult { documentVersion : number ; edits : server . TextEdit [ ] ; } namespace AllFixesRequest { export const type = new server . RequestType < AllFixesParams , AllFixesResult , void , void > ( 'textDocument/tslint/allFixes' ) ; } connection . onRequest ( AllFixesRequest . type , async ( params ) => { let result = undefined ; let uri = params . textDocument . uri ; let isOnSave = params . isOnSave ; let documentFixes = codeFixActions [ uri ] ; let documentVersion = - 1 ; let settings = await settingsCache . get ( uri ) ; if ( ! documentFixes ) { return undefined ; } let fixes = Object . keys ( documentFixes ) . map ( key => documentFixes [ key ] ) ; for ( let fix of fixes ) { if ( documentVersion === - 1 ) { documentVersion = fix . documentVersion ; break ; } } if ( isOnSave && settings && Array . isArray ( settings . autoFixOnSave ) ) { const autoFixOnSave = settings . autoFixOnSave as Array < string > ; fixes = fixes . filter ( fix => autoFixOnSave . indexOf ( fix . problem . getRuleName ( ) ) > - 1 ) ; } let allFixes = getAllNonOverlappingFixes ( fixes ) ; result = { documentVersion : documentVersion , edits : concatenateEdits ( allFixes ) } ; return result ; } ) ; function concatenateEdits ( fixes ) { let textEdits = [ ] ; fixes . forEach ( each => { textEdits = textEdits . concat ( createTextEdit ( each ) ) ; } ) ; return textEdits ; } function traceConfigurationFile ( configuration ) { if ( ! configuration ) { trace ( "no tslint configuration" ) ; return ; } trace ( "tslint configuration:" , util . inspect ( configuration , undefined , 4 ) ) ; } connection . listen ( ) ;