0
# Formatters
1
2
TSLint includes 13 built-in formatters for different output formats and provides APIs for creating custom formatters.
3
4
## Built-in Formatters
5
6
### Formatter Overview
7
8
TSLint formatters control how linting results are displayed. Each formatter targets specific use cases:
9
10
- **Human-readable**: `stylish`, `prose`, `verbose`, `codeFrame`
11
- **Machine-readable**: `json`, `checkstyle`, `junit`, `pmd`, `tap`
12
- **IDE integration**: `msbuild`, `vso`
13
- **Utilities**: `fileslist`
14
15
### Formatter Interface
16
17
```typescript { .api }
18
interface IFormatter {
19
format(failures: RuleFailure[], fixes?: RuleFailure[], fileNames?: string[]): string;
20
}
21
22
interface IFormatterMetadata {
23
formatterName: string;
24
description: string;
25
descriptionDetails?: string;
26
sample: string;
27
consumer: "human" | "machine";
28
}
29
30
// Available via internal import:
31
// import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
32
abstract class AbstractFormatter implements IFormatter {
33
static metadata: IFormatterMetadata;
34
abstract format(failures: RuleFailure[], fixes?: RuleFailure[], fileNames?: string[]): string;
35
protected sortFailures(failures: RuleFailure[]): RuleFailure[];
36
}
37
38
type FormatterConstructor = new () => IFormatter;
39
```
40
41
## Built-in Formatters Reference
42
43
### stylish (Default)
44
45
Human-readable format similar to ESLint's stylish formatter.
46
47
```typescript
48
// Usage
49
const linter = new Linter({ formatter: 'stylish' });
50
```
51
52
**Sample Output:**
53
```
54
src/app.ts
55
10:5 error Missing semicolon semicolon
56
15:12 warning 'console' is not allowed no-console
57
58
src/utils.ts
59
23:8 error Variable 'unused' is never used no-unused-variable
60
61
β 3 problems (2 errors, 1 warning)
62
```
63
64
### json
65
66
Machine-readable JSON format for programmatic processing.
67
68
```typescript
69
// Usage
70
const linter = new Linter({ formatter: 'json' });
71
```
72
73
**Sample Output:**
74
```json
75
[
76
{
77
"endPosition": {
78
"character": 13,
79
"line": 9,
80
"position": 185
81
},
82
"failure": "Missing semicolon",
83
"fix": {
84
"innerStart": 184,
85
"innerLength": 0,
86
"innerText": ";"
87
},
88
"name": "src/app.ts",
89
"ruleName": "semicolon",
90
"ruleSeverity": "error",
91
"startPosition": {
92
"character": 12,
93
"line": 9,
94
"position": 184
95
}
96
}
97
]
98
```
99
100
### checkstyle
101
102
Checkstyle XML format for integration with Java tools and CI systems.
103
104
```typescript
105
// Usage
106
const linter = new Linter({ formatter: 'checkstyle' });
107
```
108
109
**Sample Output:**
110
```xml
111
<?xml version="1.0" encoding="utf-8"?>
112
<checkstyle version="4.3">
113
<file name="src/app.ts">
114
<error line="10" column="5" severity="error" message="Missing semicolon" source="failure.tslint.semicolon"/>
115
<error line="15" column="12" severity="warning" message="'console' is not allowed" source="failure.tslint.no-console"/>
116
</file>
117
</checkstyle>
118
```
119
120
### junit
121
122
JUnit XML format for test result integration.
123
124
```typescript
125
// Usage
126
const linter = new Linter({ formatter: 'junit' });
127
```
128
129
**Sample Output:**
130
```xml
131
<?xml version="1.0" encoding="utf-8"?>
132
<testsuites package="tslint">
133
<testsuite name="src/app.ts" errors="2" failures="0" tests="2">
134
<testcase name="semicolon" classname="src/app.ts">
135
<error message="Missing semicolon">Line 10, Column 5</error>
136
</testcase>
137
</testsuite>
138
</testsuites>
139
```
140
141
### prose
142
143
Verbose human-readable format with detailed context.
144
145
```typescript
146
// Usage
147
const linter = new Linter({ formatter: 'prose' });
148
```
149
150
**Sample Output:**
151
```
152
ERROR: src/app.ts:10:5 - Missing semicolon
153
WARNING: src/app.ts:15:12 - 'console' is not allowed
154
ERROR: src/utils.ts:23:8 - Variable 'unused' is never used
155
```
156
157
### verbose
158
159
Detailed format showing rule names and severity levels.
160
161
```typescript
162
// Usage
163
const linter = new Linter({ formatter: 'verbose' });
164
```
165
166
**Sample Output:**
167
```
168
(semicolon) src/app.ts[10, 5]: Missing semicolon
169
(no-console) src/app.ts[15, 12]: 'console' is not allowed
170
(no-unused-variable) src/utils.ts[23, 8]: Variable 'unused' is never used
171
```
172
173
### codeFrame
174
175
Shows code context around violations with visual indicators.
176
177
```typescript
178
// Usage
179
const linter = new Linter({ formatter: 'codeFrame' });
180
```
181
182
**Sample Output:**
183
```
184
src/app.ts
185
8 | function hello() {
186
9 | const name = 'world'
187
> 10 | console.log(name)
188
| ^^^^^^^^^
189
11 | return name;
190
12 | }
191
192
Missing semicolon (semicolon)
193
```
194
195
### fileslist
196
197
Simple list of files containing violations.
198
199
```typescript
200
// Usage
201
const linter = new Linter({ formatter: 'fileslist' });
202
```
203
204
**Sample Output:**
205
```
206
src/app.ts
207
src/utils.ts
208
src/components/Header.tsx
209
```
210
211
### msbuild
212
213
Microsoft Build format for Visual Studio integration.
214
215
```typescript
216
// Usage
217
const linter = new Linter({ formatter: 'msbuild' });
218
```
219
220
**Sample Output:**
221
```
222
src/app.ts(10,5): error semicolon: Missing semicolon
223
src/app.ts(15,12): warning no-console: 'console' is not allowed
224
```
225
226
### pmd
227
228
PMD XML format for Java static analysis tool compatibility.
229
230
```typescript
231
// Usage
232
const linter = new Linter({ formatter: 'pmd' });
233
```
234
235
**Sample Output:**
236
```xml
237
<?xml version="1.0" encoding="utf-8"?>
238
<pmd version="tslint">
239
<file name="src/app.ts">
240
<violation begincolumn="5" beginline="10" priority="1" rule="semicolon">
241
Missing semicolon
242
</violation>
243
</file>
244
</pmd>
245
```
246
247
### tap
248
249
Test Anything Protocol format for test harness integration.
250
251
```typescript
252
// Usage
253
const linter = new Linter({ formatter: 'tap' });
254
```
255
256
**Sample Output:**
257
```
258
TAP version 13
259
1..3
260
not ok 1 - src/app.ts:10:5 semicolon
261
not ok 2 - src/app.ts:15:12 no-console
262
not ok 3 - src/utils.ts:23:8 no-unused-variable
263
```
264
265
### vso
266
267
Visual Studio Online / Azure DevOps format.
268
269
```typescript
270
// Usage
271
const linter = new Linter({ formatter: 'vso' });
272
```
273
274
**Sample Output:**
275
```
276
##vso[task.logissue type=error;sourcepath=src/app.ts;linenumber=10;columnnumber=5;]Missing semicolon
277
##vso[task.logissue type=warning;sourcepath=src/app.ts;linenumber=15;columnnumber=12;]'console' is not allowed
278
```
279
280
## Custom Formatter Development
281
282
### Basic Custom Formatter
283
284
```typescript
285
import { IFormatter, IFormatterMetadata, RuleFailure } from 'tslint';
286
287
export class Formatter implements IFormatter {
288
public static metadata: IFormatterMetadata = {
289
formatterName: 'my-custom',
290
description: 'Custom formatter example',
291
descriptionDetails: 'Formats violations with custom styling',
292
sample: 'ERROR: Missing semicolon at src/app.ts:10:5',
293
consumer: 'human'
294
};
295
296
public format(failures: RuleFailure[]): string {
297
const sortedFailures = failures.sort((a, b) => {
298
return a.getFileName().localeCompare(b.getFileName()) ||
299
a.getStartPosition().position - b.getStartPosition().position;
300
});
301
302
return sortedFailures.map(failure => {
303
const severity = failure.getRuleSeverity().toUpperCase();
304
const fileName = failure.getFileName();
305
const position = failure.getStartPosition();
306
const message = failure.getFailure();
307
308
return `${severity}: ${message} at ${fileName}:${position.line + 1}:${position.character + 1}`;
309
}).join('\n');
310
}
311
}
312
```
313
314
### Advanced Custom Formatter with Options
315
316
```typescript
317
// Internal import required for AbstractFormatter
318
import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
319
import { IFormatterMetadata, RuleFailure } from 'tslint';
320
import * as chalk from 'chalk';
321
322
interface FormatterOptions {
323
colorOutput?: boolean;
324
showRuleNames?: boolean;
325
groupByFile?: boolean;
326
}
327
328
export class Formatter extends AbstractFormatter {
329
public static metadata: IFormatterMetadata = {
330
formatterName: 'enhanced',
331
description: 'Enhanced formatter with colors and grouping',
332
sample: 'π src/app.ts\n β Missing semicolon (semicolon)',
333
consumer: 'human'
334
};
335
336
public format(failures: RuleFailure[], fixes?: RuleFailure[]): string {
337
const sortedFailures = this.sortFailures(failures);
338
const options: FormatterOptions = {
339
colorOutput: true,
340
showRuleNames: true,
341
groupByFile: true
342
};
343
344
if (options.groupByFile) {
345
return this.formatGroupedByFile(sortedFailures, options);
346
} else {
347
return this.formatFlat(sortedFailures, options);
348
}
349
}
350
351
private formatGroupedByFile(failures: RuleFailure[], options: FormatterOptions): string {
352
const fileGroups = new Map<string, RuleFailure[]>();
353
354
failures.forEach(failure => {
355
const fileName = failure.getFileName();
356
if (!fileGroups.has(fileName)) {
357
fileGroups.set(fileName, []);
358
}
359
fileGroups.get(fileName)!.push(failure);
360
});
361
362
const output: string[] = [];
363
364
fileGroups.forEach((fileFailures, fileName) => {
365
if (options.colorOutput) {
366
output.push(chalk.blue(`π ${fileName}`));
367
} else {
368
output.push(`π ${fileName}`);
369
}
370
371
fileFailures.forEach(failure => {
372
const line = this.formatFailure(failure, options, ' ');
373
output.push(line);
374
});
375
376
output.push(''); // Empty line between files
377
});
378
379
return output.join('\n');
380
}
381
382
private formatFlat(failures: RuleFailure[], options: FormatterOptions): string {
383
return failures.map(failure => this.formatFailure(failure, options)).join('\n');
384
}
385
386
private formatFailure(failure: RuleFailure, options: FormatterOptions, indent: string = ''): string {
387
const severity = failure.getRuleSeverity();
388
const position = failure.getStartPosition();
389
const message = failure.getFailure();
390
const ruleName = failure.getRuleName();
391
392
let icon = severity === 'error' ? 'β' : 'β οΈ';
393
let line = `${indent}${icon} `;
394
395
if (options.colorOutput) {
396
const colorFn = severity === 'error' ? chalk.red : chalk.yellow;
397
line += colorFn(message);
398
} else {
399
line += message;
400
}
401
402
line += ` (${position.line + 1}:${position.character + 1})`;
403
404
if (options.showRuleNames) {
405
if (options.colorOutput) {
406
line += chalk.gray(` [${ruleName}]`);
407
} else {
408
line += ` [${ruleName}]`;
409
}
410
}
411
412
return line;
413
}
414
}
415
```
416
417
### JSON-Based Custom Formatter
418
419
```typescript
420
import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
421
import { RuleFailure } from 'tslint';
422
423
interface CustomReport {
424
summary: {
425
totalFiles: number;
426
totalFailures: number;
427
errorCount: number;
428
warningCount: number;
429
};
430
files: FileReport[];
431
}
432
433
interface FileReport {
434
path: string;
435
failures: FailureReport[];
436
}
437
438
interface FailureReport {
439
rule: string;
440
severity: string;
441
message: string;
442
line: number;
443
column: number;
444
hasFix: boolean;
445
}
446
447
export class Formatter extends AbstractFormatter {
448
public static metadata = {
449
formatterName: 'detailed-json',
450
description: 'Detailed JSON formatter with statistics',
451
sample: '{"summary":{"totalFiles":2,"totalFailures":3},"files":[...]}',
452
consumer: 'machine' as const
453
};
454
455
public format(failures: RuleFailure[], fixes?: RuleFailure[], fileNames?: string[]): string {
456
const fileGroups = this.groupFailuresByFile(failures);
457
const report: CustomReport = {
458
summary: this.generateSummary(failures, fileGroups.size),
459
files: Array.from(fileGroups.entries()).map(([path, fileFailures]) => ({
460
path,
461
failures: fileFailures.map(this.mapFailure)
462
}))
463
};
464
465
return JSON.stringify(report, null, 2);
466
}
467
468
private groupFailuresByFile(failures: RuleFailure[]): Map<string, RuleFailure[]> {
469
const groups = new Map<string, RuleFailure[]>();
470
471
failures.forEach(failure => {
472
const fileName = failure.getFileName();
473
if (!groups.has(fileName)) {
474
groups.set(fileName, []);
475
}
476
groups.get(fileName)!.push(failure);
477
});
478
479
return groups;
480
}
481
482
private generateSummary(failures: RuleFailure[], fileCount: number) {
483
const errorCount = failures.filter(f => f.getRuleSeverity() === 'error').length;
484
const warningCount = failures.filter(f => f.getRuleSeverity() === 'warning').length;
485
486
return {
487
totalFiles: fileCount,
488
totalFailures: failures.length,
489
errorCount,
490
warningCount
491
};
492
}
493
494
private mapFailure = (failure: RuleFailure): FailureReport => {
495
const position = failure.getStartPosition();
496
497
return {
498
rule: failure.getRuleName(),
499
severity: failure.getRuleSeverity(),
500
message: failure.getFailure(),
501
line: position.line + 1,
502
column: position.character + 1,
503
hasFix: failure.hasFix()
504
};
505
};
506
}
507
```
508
509
## Formatter Loading and Usage
510
511
### Loading Custom Formatters
512
513
```typescript
514
import { Configuration, Linter } from 'tslint';
515
516
// Load formatter from formatters directory
517
const linter = new Linter({
518
fix: false,
519
formatter: 'my-custom-formatter',
520
formattersDirectory: './formatters'
521
});
522
523
// Use formatter constructor directly
524
import { MyCustomFormatter } from './formatters/myCustomFormatter';
525
526
const linter2 = new Linter({
527
fix: false,
528
formatter: MyCustomFormatter
529
});
530
```
531
532
### CLI Usage
533
534
```bash
535
# Use built-in formatter
536
tslint -s stylish src/**/*.ts
537
538
# Use custom formatter
539
tslint -s my-custom --formatters-dir ./formatters src/**/*.ts
540
541
# Output to file
542
tslint -s json -o results.json src/**/*.ts
543
```
544
545
### Configuration File Usage
546
547
```json
548
{
549
"linterOptions": {
550
"format": "stylish"
551
}
552
}
553
```
554
555
## Advanced Formatter Features
556
557
### Handling Fixes in Formatters
558
559
```typescript
560
export class Formatter extends AbstractFormatter {
561
public format(failures: RuleFailure[], fixes?: RuleFailure[]): string {
562
const output: string[] = [];
563
564
// Format regular failures
565
if (failures.length > 0) {
566
output.push('Violations found:');
567
failures.forEach(failure => {
568
output.push(` ${this.formatFailure(failure)}`);
569
});
570
}
571
572
// Format fixes applied
573
if (fixes && fixes.length > 0) {
574
output.push('\nFixes applied:');
575
fixes.forEach(fix => {
576
output.push(` Fixed: ${fix.getFailure()}`);
577
});
578
}
579
580
return output.join('\n');
581
}
582
}
583
```
584
585
### Integration with External Tools
586
587
```typescript
588
import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
589
import { RuleFailure } from 'tslint';
590
import * as fs from 'fs';
591
import * as path from 'path';
592
593
export class Formatter extends AbstractFormatter {
594
public format(failures: RuleFailure[]): string {
595
// Generate SARIF format for GitHub Security tab
596
const sarif = {
597
version: '2.1.0',
598
runs: [{
599
tool: {
600
driver: {
601
name: 'TSLint',
602
version: '6.1.3'
603
}
604
},
605
results: failures.map(failure => ({
606
ruleId: failure.getRuleName(),
607
level: failure.getRuleSeverity() === 'error' ? 'error' : 'warning',
608
message: { text: failure.getFailure() },
609
locations: [{
610
physicalLocation: {
611
artifactLocation: {
612
uri: path.relative(process.cwd(), failure.getFileName())
613
},
614
region: {
615
startLine: failure.getStartPosition().line + 1,
616
startColumn: failure.getStartPosition().character + 1
617
}
618
}
619
}]
620
}))
621
}]
622
};
623
624
return JSON.stringify(sarif, null, 2);
625
}
626
}
627
```
628
629
### Performance-Optimized Formatter
630
631
```typescript
632
import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
633
import { RuleFailure } from 'tslint';
634
635
export class Formatter extends AbstractFormatter {
636
private static formatCache = new Map<string, string>();
637
638
public format(failures: RuleFailure[]): string {
639
// Use string concatenation for better performance with large datasets
640
let output = '';
641
const cacheKey = this.getCacheKey(failures);
642
643
if (Formatter.formatCache.has(cacheKey)) {
644
return Formatter.formatCache.get(cacheKey)!;
645
}
646
647
const sortedFailures = this.sortFailures(failures);
648
649
for (const failure of sortedFailures) {
650
output += this.formatFailureEfficient(failure) + '\n';
651
}
652
653
Formatter.formatCache.set(cacheKey, output);
654
return output;
655
}
656
657
private getCacheKey(failures: RuleFailure[]): string {
658
// Simple cache key based on failure count and first/last failure
659
if (failures.length === 0) return 'empty';
660
661
const first = failures[0];
662
const last = failures[failures.length - 1];
663
664
return `${failures.length}-${first.getRuleName()}-${last.getRuleName()}`;
665
}
666
667
private formatFailureEfficient(failure: RuleFailure): string {
668
// Pre-computed format string for efficiency
669
const pos = failure.getStartPosition();
670
return `${failure.getFileName()}:${pos.line + 1}:${pos.character + 1} ${failure.getRuleSeverity()} ${failure.getFailure()}`;
671
}
672
}
673
```
674
675
## Best Practices
676
677
### Formatter Development Guidelines
678
679
1. **Extend AbstractFormatter** (from internal path) for built-in sorting utilities, or implement IFormatter directly
680
2. **Provide complete metadata** with clear descriptions and samples
681
3. **Handle edge cases** like empty failure arrays gracefully
682
4. **Consider performance** for large codebases
683
5. **Follow output conventions** for your target consumer (human vs machine)
684
6. **Support both failures and fixes** in your format method
685
7. **Use consistent error codes** for machine-readable formats
686
687
### Testing Custom Formatters
688
689
```typescript
690
import { Formatter } from './myCustomFormatter';
691
import { RuleFailure, RuleFailurePosition } from 'tslint';
692
import * as ts from 'typescript';
693
694
describe('MyCustomFormatter', () => {
695
it('should format failures correctly', () => {
696
const sourceFile = ts.createSourceFile('test.ts', 'console.log("test");', ts.ScriptTarget.Latest);
697
const failure = new RuleFailure(
698
sourceFile,
699
0,
700
10,
701
'Test failure message',
702
'test-rule'
703
);
704
705
const formatter = new Formatter();
706
const result = formatter.format([failure]);
707
708
expect(result).toContain('test.ts');
709
expect(result).toContain('Test failure message');
710
});
711
});
712
```