CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tslint

An extensible static analysis linter for the TypeScript language

Pending
Overview
Eval results
Files

formatters.mddocs/

Formatters

TSLint includes 13 built-in formatters for different output formats and provides APIs for creating custom formatters.

Built-in Formatters

Formatter Overview

TSLint formatters control how linting results are displayed. Each formatter targets specific use cases:

  • Human-readable: stylish, prose, verbose, codeFrame
  • Machine-readable: json, checkstyle, junit, pmd, tap
  • IDE integration: msbuild, vso
  • Utilities: fileslist

Formatter Interface

interface IFormatter {
    format(failures: RuleFailure[], fixes?: RuleFailure[], fileNames?: string[]): string;
}

interface IFormatterMetadata {
    formatterName: string;
    description: string;
    descriptionDetails?: string;
    sample: string;
    consumer: "human" | "machine";
}

// Available via internal import:
// import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
abstract class AbstractFormatter implements IFormatter {
    static metadata: IFormatterMetadata;
    abstract format(failures: RuleFailure[], fixes?: RuleFailure[], fileNames?: string[]): string;
    protected sortFailures(failures: RuleFailure[]): RuleFailure[];
}

type FormatterConstructor = new () => IFormatter;

Built-in Formatters Reference

stylish (Default)

Human-readable format similar to ESLint's stylish formatter.

// Usage
const linter = new Linter({ formatter: 'stylish' });

Sample Output:

src/app.ts
  10:5   error    Missing semicolon                 semicolon
  15:12  warning  'console' is not allowed         no-console
  
src/utils.ts  
  23:8   error    Variable 'unused' is never used  no-unused-variable

✖ 3 problems (2 errors, 1 warning)

json

Machine-readable JSON format for programmatic processing.

// Usage
const linter = new Linter({ formatter: 'json' });

Sample Output:

[
  {
    "endPosition": {
      "character": 13,
      "line": 9,
      "position": 185
    },
    "failure": "Missing semicolon",
    "fix": {
      "innerStart": 184,
      "innerLength": 0,
      "innerText": ";"
    },
    "name": "src/app.ts",
    "ruleName": "semicolon",
    "ruleSeverity": "error",
    "startPosition": {
      "character": 12,
      "line": 9,
      "position": 184
    }
  }
]

checkstyle

Checkstyle XML format for integration with Java tools and CI systems.

// Usage  
const linter = new Linter({ formatter: 'checkstyle' });

Sample Output:

<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
  <file name="src/app.ts">
    <error line="10" column="5" severity="error" message="Missing semicolon" source="failure.tslint.semicolon"/>
    <error line="15" column="12" severity="warning" message="'console' is not allowed" source="failure.tslint.no-console"/>
  </file>
</checkstyle>

junit

JUnit XML format for test result integration.

// Usage
const linter = new Linter({ formatter: 'junit' });

Sample Output:

<?xml version="1.0" encoding="utf-8"?>
<testsuites package="tslint">
  <testsuite name="src/app.ts" errors="2" failures="0" tests="2">
    <testcase name="semicolon" classname="src/app.ts">
      <error message="Missing semicolon">Line 10, Column 5</error>
    </testcase>
  </testsuite>
</testsuites>

prose

Verbose human-readable format with detailed context.

// Usage
const linter = new Linter({ formatter: 'prose' });

Sample Output:

ERROR: src/app.ts:10:5 - Missing semicolon
WARNING: src/app.ts:15:12 - 'console' is not allowed
ERROR: src/utils.ts:23:8 - Variable 'unused' is never used

verbose

Detailed format showing rule names and severity levels.

// Usage
const linter = new Linter({ formatter: 'verbose' });

Sample Output:

(semicolon) src/app.ts[10, 5]: Missing semicolon  
(no-console) src/app.ts[15, 12]: 'console' is not allowed
(no-unused-variable) src/utils.ts[23, 8]: Variable 'unused' is never used

codeFrame

Shows code context around violations with visual indicators.

// Usage
const linter = new Linter({ formatter: 'codeFrame' });

Sample Output:

src/app.ts
   8 | function hello() {
   9 |     const name = 'world'
> 10 |     console.log(name)
     |             ^^^^^^^^^
  11 |     return name;
  12 | }

  Missing semicolon (semicolon)

fileslist

Simple list of files containing violations.

// Usage
const linter = new Linter({ formatter: 'fileslist' });

Sample Output:

src/app.ts
src/utils.ts
src/components/Header.tsx

msbuild

Microsoft Build format for Visual Studio integration.

// Usage
const linter = new Linter({ formatter: 'msbuild' });

Sample Output:

src/app.ts(10,5): error semicolon: Missing semicolon
src/app.ts(15,12): warning no-console: 'console' is not allowed

pmd

PMD XML format for Java static analysis tool compatibility.

// Usage
const linter = new Linter({ formatter: 'pmd' });

Sample Output:

<?xml version="1.0" encoding="utf-8"?>
<pmd version="tslint">
  <file name="src/app.ts">
    <violation begincolumn="5" beginline="10" priority="1" rule="semicolon">
      Missing semicolon
    </violation>
  </file>  
</pmd>

tap

Test Anything Protocol format for test harness integration.

// Usage
const linter = new Linter({ formatter: 'tap' });

Sample Output:

TAP version 13
1..3
not ok 1 - src/app.ts:10:5 semicolon
not ok 2 - src/app.ts:15:12 no-console  
not ok 3 - src/utils.ts:23:8 no-unused-variable

vso

Visual Studio Online / Azure DevOps format.

// Usage
const linter = new Linter({ formatter: 'vso' });

Sample Output:

##vso[task.logissue type=error;sourcepath=src/app.ts;linenumber=10;columnnumber=5;]Missing semicolon
##vso[task.logissue type=warning;sourcepath=src/app.ts;linenumber=15;columnnumber=12;]'console' is not allowed

Custom Formatter Development

Basic Custom Formatter

import { IFormatter, IFormatterMetadata, RuleFailure } from 'tslint';

export class Formatter implements IFormatter {
    public static metadata: IFormatterMetadata = {
        formatterName: 'my-custom',
        description: 'Custom formatter example',
        descriptionDetails: 'Formats violations with custom styling',
        sample: 'ERROR: Missing semicolon at src/app.ts:10:5',
        consumer: 'human'
    };

    public format(failures: RuleFailure[]): string {
        const sortedFailures = failures.sort((a, b) => {
            return a.getFileName().localeCompare(b.getFileName()) ||
                   a.getStartPosition().position - b.getStartPosition().position;
        });
        
        return sortedFailures.map(failure => {
            const severity = failure.getRuleSeverity().toUpperCase();
            const fileName = failure.getFileName();
            const position = failure.getStartPosition();
            const message = failure.getFailure();
            
            return `${severity}: ${message} at ${fileName}:${position.line + 1}:${position.character + 1}`;
        }).join('\n');
    }
}

Advanced Custom Formatter with Options

// Internal import required for AbstractFormatter
import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
import { IFormatterMetadata, RuleFailure } from 'tslint';
import * as chalk from 'chalk';

interface FormatterOptions {
    colorOutput?: boolean;
    showRuleNames?: boolean;
    groupByFile?: boolean;
}

export class Formatter extends AbstractFormatter {
    public static metadata: IFormatterMetadata = {
        formatterName: 'enhanced',
        description: 'Enhanced formatter with colors and grouping',
        sample: '📁 src/app.ts\n  ❌ Missing semicolon (semicolon)',
        consumer: 'human'
    };

    public format(failures: RuleFailure[], fixes?: RuleFailure[]): string {
        const sortedFailures = this.sortFailures(failures);
        const options: FormatterOptions = {
            colorOutput: true,
            showRuleNames: true,
            groupByFile: true
        };

        if (options.groupByFile) {
            return this.formatGroupedByFile(sortedFailures, options);
        } else {
            return this.formatFlat(sortedFailures, options);
        }
    }

    private formatGroupedByFile(failures: RuleFailure[], options: FormatterOptions): string {
        const fileGroups = new Map<string, RuleFailure[]>();
        
        failures.forEach(failure => {
            const fileName = failure.getFileName();
            if (!fileGroups.has(fileName)) {
                fileGroups.set(fileName, []);
            }
            fileGroups.get(fileName)!.push(failure);
        });

        const output: string[] = [];
        
        fileGroups.forEach((fileFailures, fileName) => {
            if (options.colorOutput) {
                output.push(chalk.blue(`📁 ${fileName}`));
            } else {
                output.push(`📁 ${fileName}`);
            }
            
            fileFailures.forEach(failure => {
                const line = this.formatFailure(failure, options, '  ');
                output.push(line);
            });
            
            output.push(''); // Empty line between files
        });

        return output.join('\n');
    }

    private formatFlat(failures: RuleFailure[], options: FormatterOptions): string {
        return failures.map(failure => this.formatFailure(failure, options)).join('\n');
    }

    private formatFailure(failure: RuleFailure, options: FormatterOptions, indent: string = ''): string {
        const severity = failure.getRuleSeverity();
        const position = failure.getStartPosition();
        const message = failure.getFailure();
        const ruleName = failure.getRuleName();
        
        let icon = severity === 'error' ? '❌' : '⚠️';
        let line = `${indent}${icon} `;
        
        if (options.colorOutput) {
            const colorFn = severity === 'error' ? chalk.red : chalk.yellow;
            line += colorFn(message);
        } else {
            line += message;
        }
        
        line += ` (${position.line + 1}:${position.character + 1})`;
        
        if (options.showRuleNames) {
            if (options.colorOutput) {
                line += chalk.gray(` [${ruleName}]`);
            } else {
                line += ` [${ruleName}]`;
            }
        }
        
        return line;
    }
}

JSON-Based Custom Formatter

import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
import { RuleFailure } from 'tslint';

interface CustomReport {
    summary: {
        totalFiles: number;
        totalFailures: number;
        errorCount: number;
        warningCount: number;
    };
    files: FileReport[];
}

interface FileReport {
    path: string;
    failures: FailureReport[];
}

interface FailureReport {
    rule: string;
    severity: string;
    message: string;
    line: number;
    column: number;
    hasFix: boolean;
}

export class Formatter extends AbstractFormatter {
    public static metadata = {
        formatterName: 'detailed-json',
        description: 'Detailed JSON formatter with statistics',
        sample: '{"summary":{"totalFiles":2,"totalFailures":3},"files":[...]}',
        consumer: 'machine' as const
    };

    public format(failures: RuleFailure[], fixes?: RuleFailure[], fileNames?: string[]): string {
        const fileGroups = this.groupFailuresByFile(failures);
        const report: CustomReport = {
            summary: this.generateSummary(failures, fileGroups.size),
            files: Array.from(fileGroups.entries()).map(([path, fileFailures]) => ({
                path,
                failures: fileFailures.map(this.mapFailure)
            }))
        };

        return JSON.stringify(report, null, 2);
    }

    private groupFailuresByFile(failures: RuleFailure[]): Map<string, RuleFailure[]> {
        const groups = new Map<string, RuleFailure[]>();
        
        failures.forEach(failure => {
            const fileName = failure.getFileName();
            if (!groups.has(fileName)) {
                groups.set(fileName, []);
            }
            groups.get(fileName)!.push(failure);
        });
        
        return groups;
    }

    private generateSummary(failures: RuleFailure[], fileCount: number) {
        const errorCount = failures.filter(f => f.getRuleSeverity() === 'error').length;
        const warningCount = failures.filter(f => f.getRuleSeverity() === 'warning').length;
        
        return {
            totalFiles: fileCount,
            totalFailures: failures.length,
            errorCount,
            warningCount
        };
    }

    private mapFailure = (failure: RuleFailure): FailureReport => {
        const position = failure.getStartPosition();
        
        return {
            rule: failure.getRuleName(),
            severity: failure.getRuleSeverity(),
            message: failure.getFailure(),
            line: position.line + 1,
            column: position.character + 1,
            hasFix: failure.hasFix()
        };
    };
}

Formatter Loading and Usage

Loading Custom Formatters

import { Configuration, Linter } from 'tslint';

// Load formatter from formatters directory
const linter = new Linter({
    fix: false,
    formatter: 'my-custom-formatter',
    formattersDirectory: './formatters'
});

// Use formatter constructor directly
import { MyCustomFormatter } from './formatters/myCustomFormatter';

const linter2 = new Linter({
    fix: false,
    formatter: MyCustomFormatter
});

CLI Usage

# Use built-in formatter
tslint -s stylish src/**/*.ts

# Use custom formatter
tslint -s my-custom --formatters-dir ./formatters src/**/*.ts

# Output to file
tslint -s json -o results.json src/**/*.ts

Configuration File Usage

{
    "linterOptions": {
        "format": "stylish"
    }
}

Advanced Formatter Features

Handling Fixes in Formatters

export class Formatter extends AbstractFormatter {
    public format(failures: RuleFailure[], fixes?: RuleFailure[]): string {
        const output: string[] = [];
        
        // Format regular failures
        if (failures.length > 0) {
            output.push('Violations found:');
            failures.forEach(failure => {
                output.push(`  ${this.formatFailure(failure)}`);
            });
        }
        
        // Format fixes applied
        if (fixes && fixes.length > 0) {
            output.push('\nFixes applied:');
            fixes.forEach(fix => {
                output.push(`  Fixed: ${fix.getFailure()}`);
            });
        }
        
        return output.join('\n');
    }
}

Integration with External Tools

import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
import { RuleFailure } from 'tslint';
import * as fs from 'fs';
import * as path from 'path';

export class Formatter extends AbstractFormatter {
    public format(failures: RuleFailure[]): string {
        // Generate SARIF format for GitHub Security tab
        const sarif = {
            version: '2.1.0',
            runs: [{
                tool: {
                    driver: {
                        name: 'TSLint',
                        version: '6.1.3'
                    }
                },
                results: failures.map(failure => ({
                    ruleId: failure.getRuleName(),
                    level: failure.getRuleSeverity() === 'error' ? 'error' : 'warning',
                    message: { text: failure.getFailure() },
                    locations: [{
                        physicalLocation: {
                            artifactLocation: {
                                uri: path.relative(process.cwd(), failure.getFileName())
                            },
                            region: {
                                startLine: failure.getStartPosition().line + 1,
                                startColumn: failure.getStartPosition().character + 1
                            }
                        }
                    }]
                }))
            }]
        };
        
        return JSON.stringify(sarif, null, 2);
    }
}

Performance-Optimized Formatter

import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
import { RuleFailure } from 'tslint';

export class Formatter extends AbstractFormatter {
    private static formatCache = new Map<string, string>();
    
    public format(failures: RuleFailure[]): string {
        // Use string concatenation for better performance with large datasets
        let output = '';
        const cacheKey = this.getCacheKey(failures);
        
        if (Formatter.formatCache.has(cacheKey)) {
            return Formatter.formatCache.get(cacheKey)!;
        }
        
        const sortedFailures = this.sortFailures(failures);
        
        for (const failure of sortedFailures) {
            output += this.formatFailureEfficient(failure) + '\n';
        }
        
        Formatter.formatCache.set(cacheKey, output);
        return output;
    }
    
    private getCacheKey(failures: RuleFailure[]): string {
        // Simple cache key based on failure count and first/last failure
        if (failures.length === 0) return 'empty';
        
        const first = failures[0];
        const last = failures[failures.length - 1];
        
        return `${failures.length}-${first.getRuleName()}-${last.getRuleName()}`;
    }
    
    private formatFailureEfficient(failure: RuleFailure): string {
        // Pre-computed format string for efficiency
        const pos = failure.getStartPosition();
        return `${failure.getFileName()}:${pos.line + 1}:${pos.character + 1} ${failure.getRuleSeverity()} ${failure.getFailure()}`;
    }
}

Best Practices

Formatter Development Guidelines

  1. Extend AbstractFormatter (from internal path) for built-in sorting utilities, or implement IFormatter directly
  2. Provide complete metadata with clear descriptions and samples
  3. Handle edge cases like empty failure arrays gracefully
  4. Consider performance for large codebases
  5. Follow output conventions for your target consumer (human vs machine)
  6. Support both failures and fixes in your format method
  7. Use consistent error codes for machine-readable formats

Testing Custom Formatters

import { Formatter } from './myCustomFormatter';
import { RuleFailure, RuleFailurePosition } from 'tslint';
import * as ts from 'typescript';

describe('MyCustomFormatter', () => {
    it('should format failures correctly', () => {
        const sourceFile = ts.createSourceFile('test.ts', 'console.log("test");', ts.ScriptTarget.Latest);
        const failure = new RuleFailure(
            sourceFile,
            0,
            10,
            'Test failure message',
            'test-rule'
        );
        
        const formatter = new Formatter();
        const result = formatter.format([failure]);
        
        expect(result).toContain('test.ts');
        expect(result).toContain('Test failure message');
    });
});

Install with Tessl CLI

npx tessl i tessl/npm-tslint

docs

cli.md

configuration.md

formatters.md

index.md

linting.md

rules.md

testing.md

tile.json