CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tslint

An extensible static analysis linter for the TypeScript language

Pending
Overview
Eval results
Files

rules.mddocs/

Rules

TSLint includes 163+ built-in rules organized by category and provides comprehensive APIs for developing custom rules.

Built-in Rules

Rule Categories

TSLint rules are organized into five categories:

  1. Functionality (65 rules) - Correctness and best practices
  2. Maintainability (14 rules) - Code maintainability
  3. Style (79 rules) - Coding style and conventions
  4. TypeScript (rules in other categories marked as TypeScript-specific)
  5. Formatting (5 rules) - Code formatting

Functionality Rules

These rules catch common errors and enforce best practices:

Core Functionality Rules

  • await-promise - Requires that the results of async functions be awaited
  • ban-comma-operator - Disallows the comma operator
  • ban-ts-ignore - Bans @ts-ignore comments from being used
  • ban - Bans the use of specific functions or global methods
  • curly - Enforces braces for if/for/do/while statements
  • forin - Requires a for ... in statement to be filtered with an if statement
  • function-constructor - Prevents using the built-in Function constructor
  • import-blacklist - Disallows importing the specified modules
  • label-position - Only allows labels in sensible locations
  • no-arg - Disallows use of arguments.caller and arguments.callee

Variable and Scope Rules

  • no-duplicate-variable - Disallows duplicate variable declarations
  • no-shadowed-variable - Disallows shadowing variable declarations
  • no-unused-expression - Disallows unused expression statements
  • no-unused-variable - Disallows unused imports, variables, functions and private members
  • no-use-before-declare - Disallows usage of variables before their declaration
  • no-var-keyword - Disallows usage of the var keyword
  • no-var-requires - Disallows the use of require statements except in import statements

Type Safety Rules

  • no-unsafe-any - Warns when using an expression of type 'any' in unsafe way
  • no-unsafe-finally - Disallows control flow statements in finally blocks
  • restrict-plus-operands - Requires both operands of addition to be the same type
  • strict-boolean-expressions - Restricts the types allowed in boolean expressions
  • strict-type-predicates - Warns for type predicates that are always true or always false
  • use-isnan - Enforces use of the isNaN() function

Maintainability Rules

Rules focused on code maintainability and complexity:

  • cyclomatic-complexity - Enforces a threshold of cyclomatic complexity
  • deprecation - Warns when deprecated APIs are used
  • max-classes-per-file - Maximum number of classes per file
  • max-file-line-count - Requires files to remain under a certain number of lines
  • max-line-length - Requires lines to be under a certain max length
  • no-default-export - Disallows default exports in ES6-style modules
  • no-default-import - Avoid import statements with side-effect imports
  • no-duplicate-imports - Disallows multiple import statements from the same module
  • no-mergeable-namespace - Disallows mergeable namespaces in the same file
  • no-require-imports - Disallows invocation of require()
  • object-literal-sort-keys - Checks ordering of keys in object literals
  • prefer-const - Requires that variable declarations use const instead of let if possible
  • prefer-readonly - Requires that private variables are marked as readonly if they're never modified outside of the constructor
  • trailing-comma - Requires or disallows trailing commas in array and object literals, destructuring assignments, function typings, named imports and exports and function parameters

Style Rules

Rules for consistent coding style:

Naming and Casing

  • class-name - Enforces PascalCase for classes and interfaces
  • file-name-casing - Enforces consistent file naming conventions
  • interface-name - Requires interface names to begin with a capital 'I'
  • match-default-export-name - Requires that a default import have the same name as the declaration it imports
  • variable-name - Checks variable names for various errors

Comments and Documentation

  • comment-format - Enforces formatting rules for single-line comments
  • comment-type - Requires or forbids JSDoc style comments for classes, interfaces, functions, etc.
  • completed-docs - Enforces documentation for important items be filled out
  • file-header - Enforces a certain header comment for all files, matched by a regular expression
  • jsdoc-format - Enforces basic format rules for JSDoc comments
  • no-redundant-jsdoc - Forbids JSDoc which duplicates TypeScript functionality

Import and Module Style

  • import-spacing - Ensures proper spacing between import statement elements
  • no-import-side-effect - Avoid import statements with side-effect imports
  • no-reference - Disallows /// <reference> imports
  • no-reference-import - Don't <reference types="foo" /> if you import foo anyway
  • ordered-imports - Requires that import statements be alphabetized and grouped

Formatting Rules

Rules for consistent code formatting:

  • eofline - Ensures the file ends with a newline
  • indent - Enforces indentation with tabs or spaces
  • linebreak-style - Enforces consistent linebreak style
  • semicolon - Enforces consistent semicolon usage
  • whitespace - Enforces whitespace style conventions

Custom Rule Development

TSLint provides comprehensive APIs for developing custom rules.

Rule Base Classes

import { Rules } from 'tslint';

// Base rule class
abstract class AbstractRule implements IRule {
    static metadata: IRuleMetadata;
    
    constructor(options: IOptions)
    getOptions(): IOptions
    isEnabled(): boolean
    apply(sourceFile: ts.SourceFile): RuleFailure[]
    applyWithWalker(walker: IWalker): RuleFailure[]
    
    // Helper method for simple rules
    applyWithFunction(
        sourceFile: ts.SourceFile,
        walkFn: (ctx: WalkContext<T>) => void,
        options?: T
    ): RuleFailure[]
}

// Type-aware rule base class  
abstract class TypedRule extends AbstractRule implements ITypedRule {
    applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[]
}

// Optional type-aware rule base class
abstract class OptionallyTypedRule extends AbstractRule {
    apply(sourceFile: ts.SourceFile): RuleFailure[]
    applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[]
}

Rule Metadata

interface IRuleMetadata {
    ruleName: string;
    type: "functionality" | "maintainability" | "style" | "typescript" | "formatting";
    deprecationMessage?: string;
    description: string;
    descriptionDetails?: string;
    hasFix?: boolean;
    optionsDescription: string;
    options: any;
    optionExamples?: Array<true | any[]> | string[] | Array<{ options: any }>;
    rationale?: string;
    requiresTypeInfo?: boolean;
    typescriptOnly: boolean;
    codeExamples?: ICodeExample[];
}

interface ICodeExample {
    description: string;
    config: string;
    pass?: string;
    fail?: string;
}

Rule Options Interface

interface IOptions {
    ruleArguments: any[];
    ruleSeverity: "warning" | "error" | "off";
    ruleName: string;
    disabledIntervals: IDisabledInterval[];
}

Rule Failure System

class RuleFailure {
    constructor(
        sourceFile: ts.SourceFile,
        start: number,
        end: number,
        failure: string,
        ruleName: string,
        fix?: Fix
    )
    
    // Position methods
    getFileName(): string
    getStartPosition(): RuleFailurePosition  
    getEndPosition(): RuleFailurePosition
    
    // Content methods
    getFailure(): string
    getRuleName(): string
    getRuleSeverity(): RuleSeverity
    setRuleSeverity(value: RuleSeverity): void
    
    // Fix methods
    hasFix(): boolean
    getFix(): Fix | undefined
    
    // Serialization
    toJson(): IRuleFailureJson
    equals(other: RuleFailure): boolean
    static compare(a: RuleFailure, b: RuleFailure): number
}

interface RuleFailurePosition {
    character: number;
    line: number;
    position: number;
}

interface IRuleFailureJson {
    endPosition: IRuleFailurePositionJson;
    failure: string;
    fix?: FixJson;
    name: string;
    ruleSeverity: string;
    ruleName: string;
    startPosition: IRuleFailurePositionJson;
}

interface IRuleFailurePositionJson {
    character: number;
    line: number;
    position: number;
}

interface ReplacementJson {
    innerStart: number;
    innerLength: number;
    innerText: string;
}

type FixJson = ReplacementJson | ReplacementJson[];
type RuleSeverity = "warning" | "error" | "off";
type Fix = Replacement | Replacement[];

Replacement System

class Replacement {
    constructor(start: number, length: number, text: string)
    
    // Static factory methods
    static replaceNode(node: ts.Node, text: string, sourceFile?: ts.SourceFile): Replacement
    static replaceFromTo(start: number, end: number, text: string): Replacement
    static deleteText(start: number, length: number): Replacement
    static deleteFromTo(start: number, end: number): Replacement
    static appendText(start: number, text: string): Replacement
    
    // Application methods
    apply(content: string): string
    static applyAll(content: string, replacements: Replacement[]): string
    static applyFixes(content: string, fixes: Fix[]): string
}

Custom Rule Examples

Simple Rule Example

import { Rules, RuleFailure, RuleWalker } from 'tslint';
import * as ts from 'typescript';

export class Rule extends Rules.AbstractRule {
    public static metadata: Rules.IRuleMetadata = {
        ruleName: 'no-console-log',
        description: 'Disallows console.log() calls',
        optionsDescription: 'Not configurable.',
        options: null,
        optionExamples: [true],
        type: 'functionality',
        typescriptOnly: false,
    };

    public apply(sourceFile: ts.SourceFile): RuleFailure[] {
        return this.applyWithFunction(sourceFile, walk);
    }
}

function walk(ctx: Rules.WalkContext<void>) {
    function cb(node: ts.Node): void {
        if (ts.isCallExpression(node) && 
            ts.isPropertyAccessExpression(node.expression) &&
            node.expression.expression.getText() === 'console' &&
            node.expression.name.text === 'log') {
            
            ctx.addFailureAtNode(node, 'console.log() is not allowed');
        }
        return ts.forEachChild(node, cb);
    }
    return ts.forEachChild(ctx.sourceFile, cb);
}

Rule with Options

import { Rules, RuleFailure } from 'tslint';
import * as ts from 'typescript';

interface Options {
    maxLength: number;
}

export class Rule extends Rules.AbstractRule {
    public static metadata: Rules.IRuleMetadata = {
        ruleName: 'max-identifier-length',
        description: 'Enforces maximum identifier length',
        optionsDescription: 'Maximum identifier length (default: 50)',
        options: {
            type: 'object',
            properties: {
                maxLength: { type: 'number' }
            }
        },
        optionExamples: [
            [true, { maxLength: 30 }]
        ],
        type: 'style',
        typescriptOnly: false,
    };

    public apply(sourceFile: ts.SourceFile): RuleFailure[] {
        const options: Options = {
            maxLength: 50,
            ...this.ruleArguments[0]
        };
        
        return this.applyWithFunction(sourceFile, walk, options);
    }
}

function walk(ctx: Rules.WalkContext<Options>) {
    function cb(node: ts.Node): void {
        if (ts.isIdentifier(node) && 
            node.text.length > ctx.options.maxLength) {
            
            ctx.addFailureAtNode(
                node, 
                `Identifier '${node.text}' exceeds maximum length of ${ctx.options.maxLength}`
            );
        }
        return ts.forEachChild(node, cb);
    }
    return ts.forEachChild(ctx.sourceFile, cb);
}

Type-Aware Rule

import { Rules, RuleFailure } from 'tslint';
import * as ts from 'typescript';

export class Rule extends Rules.TypedRule {
    public static metadata: Rules.IRuleMetadata = {
        ruleName: 'no-unused-promise',
        description: 'Disallows unused Promise return values',
        optionsDescription: 'Not configurable.',
        options: null,
        optionExamples: [true],
        type: 'functionality',
        typescriptOnly: false,
        requiresTypeInfo: true,
    };

    public applyWithProgram(
        sourceFile: ts.SourceFile,
        program: ts.Program
    ): RuleFailure[] {
        return this.applyWithFunction(
            sourceFile, 
            walk, 
            undefined, 
            program.getTypeChecker()
        );
    }
}

function walk(ctx: Rules.WalkContext<void>, tc: ts.TypeChecker) {
    function cb(node: ts.Node): void {
        if (ts.isExpressionStatement(node) &&
            ts.isCallExpression(node.expression)) {
            
            const type = tc.getTypeAtLocation(node.expression);
            const typeString = tc.typeToString(type);
            
            if (typeString.includes('Promise<')) {
                ctx.addFailureAtNode(
                    node.expression,
                    'Promise return value is unused'
                );
            }
        }
        return ts.forEachChild(node, cb);
    }
    return ts.forEachChild(ctx.sourceFile, cb);
}

Rule with Auto-fix

import { Rules, RuleFailure, Replacement } from 'tslint';
import * as ts from 'typescript';

export class Rule extends Rules.AbstractRule {
    public static metadata: Rules.IRuleMetadata = {
        ruleName: 'prefer-const-assertion',
        description: 'Prefer const assertion over type annotation',
        optionsDescription: 'Not configurable.',
        options: null,
        optionExamples: [true],
        type: 'style',
        typescriptOnly: true,
        hasFix: true,
    };

    public apply(sourceFile: ts.SourceFile): RuleFailure[] {
        return this.applyWithFunction(sourceFile, walk);
    }
}

function walk(ctx: Rules.WalkContext<void>) {
    function cb(node: ts.Node): void {
        if (ts.isVariableDeclaration(node) &&
            node.type &&
            node.initializer &&
            ts.isAsExpression(node.initializer)) {
            
            const fix = [
                Replacement.deleteFromTo(node.type.pos, node.type.end),
                Replacement.replaceNode(
                    node.initializer,
                    `${node.initializer.expression.getText()} as const`
                )
            ];
            
            ctx.addFailureAtNode(
                node,
                'Prefer const assertion over type annotation',
                fix
            );
        }
        return ts.forEachChild(node, cb);
    }
    return ts.forEachChild(ctx.sourceFile, cb);
}

Walker System (Legacy)

TSLint includes a walker system for AST traversal, though applyWithFunction is now preferred.

Walker Classes

// Basic walker interface
interface IWalker {
    getSourceFile(): ts.SourceFile;
    walk(sourceFile: ts.SourceFile): void;
    getFailures(): RuleFailure[];
}

// Abstract walker base class
abstract class AbstractWalker<T = undefined> extends WalkContext<T> implements IWalker {
    abstract walk(sourceFile: ts.SourceFile): void;
    getSourceFile(): ts.SourceFile;
    getFailures(): RuleFailure[];
}

// Base syntax walker class
class SyntaxWalker {
    walk(node: ts.Node): void;
    protected visitNode(node: ts.Node): void;
    protected walkChildren(node: ts.Node): void;
}

// Legacy rule walker (deprecated)
class RuleWalker extends SyntaxWalker {
    // Prefer applyWithFunction over extending RuleWalker
}

Walk Context

class WalkContext<T = undefined> {
    constructor(sourceFile: ts.SourceFile, ruleName: string, options?: T)
    
    // Failure reporting
    addFailure(start: number, end: number, message: string, fix?: Fix): void
    addFailureAt(position: number, width: number, message: string, fix?: Fix): void
    addFailureAtNode(node: ts.Node, message: string, fix?: Fix): void
    
    // Properties
    readonly sourceFile: ts.SourceFile
    readonly failures: RuleFailure[]
    readonly options: T | undefined
    readonly ruleName: string
}

Rule Loading and Registration

Custom Rules Directory

// In tslint.json
{
    "rulesDirectory": [
        "./custom-rules",
        "node_modules/custom-tslint-rules/lib"
    ],
    "rules": {
        "my-custom-rule": true
    }
}

Rule Naming Convention

Rules should be exported with PascalCase class names and kebab-case file names:

  • File: no-console-log.ts or no-console-log.js
  • Class: export class Rule extends Rules.AbstractRule

Rule Distribution

// Package custom rules as npm module
// package.json
{
    "name": "my-tslint-rules",
    "main": "lib/index.js",
    "files": ["lib/"]
}

// lib/noConsoleLogRule.ts
export class Rule extends Rules.AbstractRule {
    // Rule implementation
}

Best Practices

Rule Development Guidelines

  1. Use applyWithFunction instead of extending RuleWalker
  2. Provide comprehensive metadata including examples and rationale
  3. Include auto-fixes where appropriate using the Replacement API
  4. Handle edge cases and provide clear error messages
  5. Test thoroughly using TSLint's testing framework
  6. Follow naming conventions for rule files and classes
  7. Document rule behavior with clear descriptions and examples

Performance Considerations

  1. Minimize AST traversal - Use targeted node visitors
  2. Cache expensive computations - Reuse TypeScript type checker results
  3. Avoid deep recursion - Use iterative approaches where possible
  4. Limit rule scope - Don't check unnecessary node types

Error Handling

function walk(ctx: Rules.WalkContext<void>) {
    function cb(node: ts.Node): void {
        try {
            // Rule logic here
            if (someCondition(node)) {
                ctx.addFailureAtNode(node, 'Rule violation');
            }
        } catch (error) {
            // Log error but don't fail linting
            console.warn(`Rule error: ${error.message}`);
        }
        return ts.forEachChild(node, cb);
    }
    return ts.forEachChild(ctx.sourceFile, cb);
}

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