Skip to content

johnsoncodehk/tsslint

Repository files navigation

TSSLint

TSSLint Logo

npm package Discord License Ask DeepWiki

TSSLint is probably the smallest linter implementation ever. Built on the TypeScript Language Server (tsserver), it provides a minimalist diagnostic extension interface with zero default rules, allowing developers to implement custom rules with minimal overhead.

Motivation

TSSLint is the spiritual successor to TSLint. We believe that direct integration with native TypeScript APIs is the most efficient way to lint TypeScript code.

General-purpose linters like ESLint, while powerful, operate as separate processes and often need to re-initialize type-checking context. This leads to a significant pain point in large-scale projects: editor lag during "Auto Fix on Save".

TSSLint solves this by running directly as a tsserver plugin. By sharing the existing TypeChecker and operating on the native TypeScript AST (without ESTree/ES parser conversion), TSSLint provides near-instant diagnostics and fixes.

Key Features

  • Project-Centric: Treats the Project (tsconfig) as a first-class citizen, enabling efficient cross-file type analysis and superior Monorepo support.
  • High Performance: Runs as a tsserver plugin, sharing the existing TypeChecker to provide near-instant diagnostics without redundant parsing.
  • Minimalist Implementation: Probably the smallest linter ever. Zero built-in rules and minimal code overhead by leveraging native TypeScript infrastructure.
  • Rule Traceability: Built-in debugging support. Jump from a reported error directly to the exact line in your rule's source code that triggered it.

How It Works

TSSLint integrates into tsserver via the TypeScript plugin system, leveraging the semantic information already computed by your editor. Operating at the project level ensures accurate and performant diagnostics.

TSSLint Architecture Diagram

Framework Support (Vue, MDX, Astro, etc.)

Since TSSLint operates directly within tsserver, it supports any framework that integrates with the TypeScript plugin system.

Tools like Vue Official (Volar), MDX, or Astro virtualize non-TypeScript files into virtual TypeScript source files for tsserver. TSSLint seamlessly accesses and lints the TypeScript code within these virtual files without any additional configuration.

TSSLint Framework Support Diagram

Getting Started

1. Install

npm install @tsslint/config --save-dev

2. Configure tsslint.config.ts

A minimal configuration looks like this. For a complete example, see the vuejs/language-tools tsslint.config.ts.

import { defineConfig } from '@tsslint/config';

export default defineConfig({
  rules: {
    // Define or import your rules here
  },
});

3. Editor Integration

  • VSCode:
    1. Install the TSSLint extension.
    2. (Optional) If you encounter issues importing tsslint.config.ts due to Node.js version mismatches, you can configure typescript.tsserver.nodePath to point to a Node.js 23.6.0+ executable.
  • Other Editors: Configure TSSLint as a plugin in your tsconfig.json:
    {
      "compilerOptions": {
        "plugins": [{ "name": "@tsslint/typescript-plugin" }]
      }
    }

Rule Authoring

Rule Example

// rules/no-debugger.ts
import { defineRule } from '@tsslint/config';

export default defineRule(({ typescript: ts, file, report }) => {
  ts.forEachChild(file, function cb(node) {
    if (node.kind === ts.SyntaxKind.DebuggerStatement) {
      report(
        'Debugger statement is not allowed.',
        node.getStart(file),
        node.getEnd()
      );
    }
    ts.forEachChild(node, cb);
  });
});

Rule Caching Mechanism

TSSLint's high performance comes from its intelligent caching strategy, which automatically distinguishes between Syntax-Aware and Type-Aware rules.

All rule diagnostics are cached by default. The cache is automatically disabled for a rule in two scenarios:

  1. Type-Aware Detection: If a rule accesses RuleContext.program (e.g., to check types), TSSLint detects it as Type-Aware. The cache for this rule is then automatically managed and invalidated to ensure accuracy.
  2. Manual Exclusion: A rule can explicitly prevent a specific diagnostic from being cached by calling report().withoutCache().

This automatic differentiation maximizes performance for simple syntax rules while maintaining correctness for complex type-aware rules.

Rule Debugging & Traceability (The .at() Magic)

TSSLint is designed to make rule debugging trivial. Every time you call report(), TSSLint automatically captures the current JavaScript stack trace and attaches it to the diagnostic as Related Information.

This means: You can click on the diagnostic in your editor and jump directly to the line in your rule's source code that triggered the report.

TSSLint Rule Traceability Demo

The .at() method is generally not needed, but is provided for advanced scenarios where you wrap report() in a helper function and need to adjust the stack depth to point to the correct logic:

// Example of advanced usage to adjust stack depth
report('message', start, end)
  .at(new Error(), 2) // Adjusts the stack index to skip the helper function's frame
  .withFix(...);

CLI Usage

The @tsslint/cli package provides a command-line tool for CI/CD and build processes.

# Lint a project
npx tsslint --project path/to/tsconfig.json

# Auto-fix violations
npx tsslint --project path/to/tsconfig.json --fix

# Lint multiple projects
npx tsslint --project packages/*/tsconfig.json --vue-project apps/web/tsconfig.json

# Using brace expansion for multiple patterns
npx tsslint --project {tsconfig.json,packages/*/tsconfig.json,extensions/*/tsconfig.json}

Tip

TSSLint focuses on diagnostic fixes and does not include a built-in formatter. It is recommended to run a dedicated formatter like Prettier, dprint, or oxfmt after running TSSLint with --fix.

Extensions & Ecosystem

Ignoring Rules

import { defineConfig, createIgnorePlugin } from '@tsslint/config';

export default defineConfig({
  rules: {
    ...
  },
  plugins: [
    createIgnorePlugin('tsslint-ignore', true)
  ],
});

Usage: Use // tsslint-ignore comments in your code.

Ecosystem Integration

TSSLint provides compatibility layers for existing linter ecosystems. The integration functions are available via @tsslint/config, and you only need to install the original linter package to use them.

ESLint

npm install [email protected] @typescript-eslint/parser --save-dev
import { defineConfig, importESLintRules } from '@tsslint/config';

export default defineConfig({
  rules: {
    ...await importESLintRules({
      'no-unused-vars': true,
      '@typescript-eslint/no-explicit-any': true,
    }),
  },
});

importESLintRules will automatically resolve and load rules from ESLint plugins (e.g., @typescript-eslint/eslint-plugin) by searching your node_modules. Plugin rules are identified by their prefix (e.g., @typescript-eslint/).

TSLint

npm install tslint --save-dev
import { defineConfig, importTSLintRules } from '@tsslint/config';

export default defineConfig({
  rules: {
    ...await importTSLintRules({
      'no-console': true,
      'member-ordering': [true, { order: 'fields-first' }],
    }),
  },
});

importTSLintRules will automatically read rulesDirectory from your tslint.json to support third-party TSLint plugins.

TSL

npm install tsl --save-dev
import { defineConfig, fromTSLRules } from '@tsslint/config';
import { core } from 'tsl';

export default defineConfig({
	rules: fromTSLRules(core.all()),
});

Technical Notes

  • Node.js: Requires 22.6.0+ (v3.0+).
  • TypeScript: Incompatible with typescript-go (v7) as it does not support Language Service Plugins.

License

MIT

About

πŸ”‹βš‘οΈ The lightest TypeScript semantic linting solution in JS

Resources

License

Stars

Watchers

Forks

Packages

No packages published