An extensible static analysis linter for the TypeScript language
—
TSLint includes 13 built-in formatters for different output formats and provides APIs for creating custom formatters.
TSLint formatters control how linting results are displayed. Each formatter targets specific use cases:
stylish, prose, verbose, codeFramejson, checkstyle, junit, pmd, tapmsbuild, vsofileslistinterface 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;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)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 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 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>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 usedDetailed 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 usedShows 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)Simple list of files containing violations.
// Usage
const linter = new Linter({ formatter: 'fileslist' });Sample Output:
src/app.ts
src/utils.ts
src/components/Header.tsxMicrosoft 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 allowedPMD 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>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-variableVisual 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 allowedimport { 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');
}
}// 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;
}
}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()
};
};
}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
});# 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{
"linterOptions": {
"format": "stylish"
}
}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');
}
}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);
}
}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()}`;
}
}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