or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconfiguration.mdformatters.mdindex.mdlinting.mdrules.mdtesting.md

formatters.mddocs/

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

```