@@ -2,16 +2,7 @@ import * as ts_module from "../node_modules/typescript/lib/tsserverlibrary";
2
2
import * as tslint from 'tslint' ;
3
3
import * as path from 'path' ;
4
4
5
- let codeFixActions = new Map < string , Map < string , tslint . RuleFailure > > ( ) ;
6
- let registeredCodeFixes = false ;
7
-
8
- let configCache = {
9
- filePath : < string > null ,
10
- configuration : < any > null ,
11
- isDefaultConfig : false ,
12
- configFilePath : < string > null
13
- } ;
14
-
5
+ // Settings for the plugin section in tsconfig.json
15
6
interface Settings {
16
7
alwaysShowRuleFailuresAsWarnings ?: boolean ;
17
8
ignoreDefinitionFiles ?: boolean ;
@@ -25,8 +16,17 @@ const TSLINT_ERROR_CODE = 2515;
25
16
function init ( modules : { typescript : typeof ts_module } ) {
26
17
const ts = modules . typescript ;
27
18
28
- // By waiting for that TypeScript provides an API to register CodeFix
29
- // we define a registerCodeFix which uses the existing ts.codefix namespace.
19
+ let codeFixActions = new Map < string , Map < string , tslint . RuleFailure > > ( ) ;
20
+ let registeredCodeFixes = false ;
21
+
22
+ let configCache = {
23
+ filePath : < string > null ,
24
+ configuration : < any > null ,
25
+ isDefaultConfig : false ,
26
+ configFilePath : < string > null
27
+ } ;
28
+
29
+ // Work around the lack of API to register a CodeFix
30
30
function registerCodeFix ( action : codefix . CodeFix ) {
31
31
return ( ts as any ) . codefix . registerCodeFix ( action ) ;
32
32
}
@@ -37,7 +37,7 @@ function init(modules: { typescript: typeof ts_module }) {
37
37
}
38
38
39
39
function registerCodeFixes ( registerCodeFix : ( action : codefix . CodeFix ) => void ) {
40
- // Code fix for tslint fixes
40
+ // Code fix for that is used for all tslint fixes
41
41
registerCodeFix ( {
42
42
errorCodes : [ TSLINT_ERROR_CODE ] ,
43
43
getCodeActions : ( _context : any ) => {
@@ -61,6 +61,7 @@ function init(modules: { typescript: typeof ts_module }) {
61
61
}
62
62
}
63
63
64
+ // key to identify a rule failure
64
65
function computeKey ( start : number , end : number ) : string {
65
66
return `[${ start } ,${ end } ]` ;
66
67
}
@@ -171,7 +172,6 @@ function init(modules: { typescript: typeof ts_module }) {
171
172
// See https://github.com/Microsoft/TypeScript/issues/15344
172
173
// Therefore we remove the rule from the configuration.
173
174
//
174
-
175
175
// In tslint 5 the rules are stored in a Map, in earlier versions they were stored in an Object
176
176
if ( config . disableNoUnusedVariableRule === true || config . disableNoUnusedVariableRule === undefined ) {
177
177
if ( configuration . rules && configuration . rules instanceof Map ) {
@@ -194,10 +194,112 @@ function init(modules: { typescript: typeof ts_module }) {
194
194
}
195
195
196
196
function captureWarnings ( message ?: any ) : void {
197
- // TODO log to a user visible log
197
+ // TODO log to a user visible log and not only the TS-Server log
198
198
info . project . projectService . logger . info ( `[tslint] ${ message } ` ) ;
199
199
}
200
200
201
+ function convertReplacementToTextChange ( repl : tslint . Replacement ) : ts . TextChange {
202
+ return {
203
+ newText : repl . text ,
204
+ span : { start : repl . start , length : repl . length }
205
+ } ;
206
+ }
207
+
208
+ function getReplacements ( fix : tslint . Fix ) : tslint . Replacement [ ] {
209
+ let replacements : tslint . Replacement [ ] = null ;
210
+ // in tslint4 a Fix has a replacement property with the Replacements
211
+ if ( ( < any > fix ) . replacements ) {
212
+ // tslint4
213
+ replacements = ( < any > fix ) . replacements ;
214
+ } else {
215
+ // in tslint 5 a Fix is a Replacement | Replacement[]
216
+ if ( ! Array . isArray ( fix ) ) {
217
+ replacements = [ < any > fix ] ;
218
+ } else {
219
+ replacements = fix ;
220
+ }
221
+ }
222
+ return replacements ;
223
+ }
224
+
225
+ function addRuleFailureFix ( fixes : ts_module . CodeAction [ ] , problem : tslint . RuleFailure , fileName : string ) {
226
+ let fix = problem . getFix ( ) ;
227
+ let replacements : tslint . Replacement [ ] = getReplacements ( fix ) ;
228
+
229
+ fixes . push ( {
230
+ description : `Fix '${ problem . getRuleName ( ) } '` ,
231
+ changes : [ {
232
+ fileName : fileName ,
233
+ textChanges : replacements . map ( each => convertReplacementToTextChange ( each ) )
234
+ } ]
235
+ } ) ;
236
+ }
237
+
238
+ function addDisableRuleFix ( fixes : ts_module . CodeAction [ ] , problem : tslint . RuleFailure , fileName : string , file : ts_module . SourceFile ) {
239
+ fixes . push ( {
240
+ description : `Disable rule '${ problem . getRuleName ( ) } '` ,
241
+ changes : [ {
242
+ fileName : fileName ,
243
+ textChanges : [ {
244
+ newText : `// tslint:disable-next-line:${ problem . getRuleName ( ) } \n` ,
245
+ span : { start : file . getLineStarts ( ) [ problem . getStartPosition ( ) . getLineAndCharacter ( ) . line ] , length : 0 }
246
+ } ]
247
+ } ]
248
+ } ) ;
249
+ }
250
+
251
+ function addOpenConfigurationFix ( fixes : ts_module . CodeAction [ ] ) {
252
+ // the Open Configuration code action is disabled since there is no specified API to open an editor
253
+ let openConfigFixEnabled = false ;
254
+ if ( openConfigFixEnabled && configCache && configCache . configFilePath ) {
255
+ fixes . push ( {
256
+ description : `Open tslint.json` ,
257
+ changes : [ {
258
+ fileName : configCache . configFilePath ,
259
+ textChanges : [ ]
260
+ } ]
261
+ } ) ;
262
+ }
263
+ }
264
+
265
+ function addAllAutoFixable ( fixes : ts_module . CodeAction [ ] , documentFixes : Map < string , tslint . RuleFailure > , fileName : string ) {
266
+ const allReplacements = getNonOverlappingReplacements ( documentFixes ) ;
267
+ fixes . push ( {
268
+ description : `Fix all auto-fixable tslint failures` ,
269
+ changes : [ {
270
+ fileName : fileName ,
271
+ textChanges : allReplacements . map ( each => convertReplacementToTextChange ( each ) )
272
+ } ]
273
+ } ) ;
274
+ }
275
+
276
+ function getReplacement ( failure : tslint . RuleFailure , at :number ) : tslint . Replacement {
277
+ return getReplacements ( failure . getFix ( ) ) [ at ] ;
278
+ }
279
+
280
+ function sortFailures ( failures : tslint . RuleFailure [ ] ) :tslint . RuleFailure [ ] {
281
+ // The failures.replacements are sorted by position, we sort on the position of the first replacement
282
+ return failures . sort ( ( a , b ) => {
283
+ return getReplacement ( a , 0 ) . start - getReplacement ( b , 0 ) . start ;
284
+ } ) ;
285
+ }
286
+
287
+ function getNonOverlappingReplacements ( documentFixes : Map < string , tslint . RuleFailure > ) : tslint . Replacement [ ] {
288
+ function overlaps ( a : tslint . Replacement , b : tslint . Replacement ) : boolean {
289
+ return a . end >= b . start ;
290
+ }
291
+
292
+ let sortedFailures = sortFailures ( [ ...documentFixes . values ( ) ] ) ;
293
+ let nonOverlapping : tslint . Replacement [ ] = [ ] ;
294
+ for ( let i = 0 ; i < sortedFailures . length ; i ++ ) {
295
+ let replacements = getReplacements ( sortedFailures [ i ] . getFix ( ) ) ;
296
+ if ( i === 0 || ! overlaps ( nonOverlapping [ nonOverlapping . length - 1 ] , replacements [ 0 ] ) ) {
297
+ nonOverlapping . push ( ...replacements )
298
+ }
299
+ }
300
+ return nonOverlapping ;
301
+ }
302
+
201
303
proxy . getSemanticDiagnostics = ( fileName : string ) => {
202
304
let prior = oldLS . getSemanticDiagnostics ( fileName ) ;
203
305
if ( prior === undefined ) {
@@ -217,7 +319,7 @@ function init(modules: { typescript: typeof ts_module }) {
217
319
try {
218
320
configuration = getConfiguration ( fileName , config . configFile ) ;
219
321
} catch ( err ) {
220
- // TODO: show the reason for the configuration failure to the user
322
+ // TODO: show the reason for the configuration failure to the user and not only in the log
221
323
// https://github.com/Microsoft/TypeScript/issues/15913
222
324
info . project . projectService . logger . info ( getConfigurationFailureMessage ( err ) )
223
325
return prior ;
@@ -226,7 +328,7 @@ function init(modules: { typescript: typeof ts_module }) {
226
328
let result : tslint . LintResult ;
227
329
228
330
// tslint writes warning messages using console.warn()
229
- // capture the warnings and write them to the tslint log
331
+ // capture the warnings and write them to the tslint plugin log
230
332
let warn = console . warn ;
231
333
console . warn = captureWarnings ;
232
334
@@ -276,58 +378,14 @@ function init(modules: { typescript: typeof ts_module }) {
276
378
if ( documentFixes ) {
277
379
let problem = documentFixes . get ( computeKey ( start , end ) ) ;
278
380
if ( problem ) {
279
- let fix = problem . getFix ( ) ;
280
- let replacements : tslint . Replacement [ ] = null ;
281
- // in tslint4 a Fix has a replacement property with the Replacements
282
- if ( ( < any > fix ) . replacements ) {
283
- // tslint4
284
- replacements = ( < any > fix ) . replacements ;
285
- } else {
286
- // in tslint 5 a Fix is a Replacement | Replacement[]
287
- if ( ! Array . isArray ( fix ) ) {
288
- replacements = [ < any > fix ] ;
289
- } else {
290
- replacements = fix ;
291
- }
292
- }
293
-
294
- // Add tslint replacements codefix
295
- const textChanges = replacements . map ( each => convertReplacementToTextChange ( each ) ) ;
296
- prior . push ( {
297
- description : `Fix '${ problem . getRuleName ( ) } '` ,
298
- changes : [ {
299
- fileName : fileName ,
300
- textChanges : textChanges
301
- } ]
302
- } ) ;
303
- const file = oldLS . getProgram ( ) . getSourceFile ( fileName ) ;
304
- // Add disable tslint rule codefix
305
- prior . push ( {
306
- description : `Disable rule '${ problem . getRuleName ( ) } '` ,
307
- changes : [ {
308
- fileName : fileName ,
309
- textChanges : [ {
310
- newText : `// tslint:disable-next-line:${ problem . getRuleName ( ) } \n` ,
311
- span : { start : file . getLineStarts ( ) [ problem . getStartPosition ( ) . getLineAndCharacter ( ) . line ] , length : 0 }
312
- }
313
- ]
314
- } ]
315
- } ) ;
381
+ addRuleFailureFix ( prior , problem , fileName ) ;
382
+ }
383
+ addAllAutoFixable ( prior , documentFixes , fileName ) ;
384
+ if ( problem ) {
385
+ addOpenConfigurationFix ( prior ) ;
386
+ addDisableRuleFix ( prior , problem , fileName , oldLS . getProgram ( ) . getSourceFile ( fileName ) ) ;
316
387
}
317
388
}
318
- // Add "Go to rule definition" tslint.json codefix
319
- /* Comment this codefix, because it doesn't work with VSCode because textChanges is empty.
320
- Hope one day https://github.com/angelozerr/tslint-language-service/issues/4 will be supported.
321
-
322
- if (configCache && configCache.configFilePath) {
323
- prior.push({
324
- description: `Open tslint.json`,
325
- changes: [{
326
- fileName: configCache.configFilePath,
327
- textChanges: []
328
- }]
329
- });
330
- }*/
331
389
return prior ;
332
390
} ;
333
391
return proxy ;
@@ -338,14 +396,8 @@ function init(modules: { typescript: typeof ts_module }) {
338
396
339
397
export = init ;
340
398
341
- function convertReplacementToTextChange ( repl : tslint . Replacement ) : ts . TextChange {
342
- return {
343
- newText : repl . text ,
344
- span : { start : repl . start , length : repl . length }
345
- } ;
346
- }
347
-
348
399
/* @internal */
400
+ // work around for missing API to register a code fix
349
401
namespace codefix {
350
402
351
403
export interface CodeFix {
0 commit comments