tsgolint is a high-performance TypeScript linter powered by typescript-go and designed for integration with Oxlint.
┌─────────────┐ ┌──────────────────────┐
│ Oxlint CLI │◄──►│ tsgolint │
│ (frontend) │ │ (backend) │
│ • Files │ │ • Type-aware rules │
│ • Config │ │ • Parallel workers │
│ • Output │ │ • TypeScript AST │
└─────────────┘ └──────────────────────┘
Frontend/Backend Architecture:
- Oxlint CLI (frontend): File discovery, configuration, output formatting, rule orchestration
- tsgolint (backend): Type-aware rule execution, TypeScript integration, and diagnostic generation
This separation allows tsgolint to focus purely on type-aware analysis while Oxlint handles all the user-facing concerns like CLI, configuration, and output formatting.
tsgolint uses typescript-go for native performance:
- Direct AST: No conversion overhead (TypeScript AST → rules)
- Native Speed: Go implementation with full TypeScript compiler
- Type-aware: Complete access to TypeScript type checker
Coordinator → [Worker Pool] → Diagnostics
↓ ↓
Files + Rules → Rule Execution → Output
- Worker Pool: The CLI uses
runtime.GOMAXPROCS(0)to set the worker count - Shared Programs: Workers share a program and file queue, with a separate checker for each checker workload
- Diagnostics: Workers report diagnostics through callbacks; headless mode streams them to Oxlint
Rules follow a visitor pattern:
var ExampleRule = rule.Rule{
Name: "example-rule",
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
return rule.RuleListeners{
ast.KindCallExpression: func(node *ast.Node) {
// Inspect the call using ctx.TypeChecker and report diagnostics.
},
}
},
}Each rule registers listeners for specific AST node types and uses the TypeScript checker for type-aware analysis.
The interfaces are defined in internal/rule/rule.go; see CONTRIBUTING.md for a complete example.
- Performance: Native compilation and direct access to typescript-go; see the measured benchmarks
- Concurrency: Excellent parallel processing primitives
- Type Safety: Compile-time checks for Go types and interfaces
- Zero Conversion: No TypeScript → ESTree overhead
- Complete Information: Access to all TypeScript-specific data
- Type Precision: Better type-aware analysis
- Clean Separation: Independent development and testing
- Focused Scope: Type-aware rules and optional TypeScript diagnostics
- Multiple Frontends: Potential for other integrations
tsgolint accesses typescript-go internals via Go's linkname directives:
Go Shims → typescript-go Internal APIs → TypeScript Compiler
Components:
shim/ast: TypeScript AST typesshim/checker: Type checker interfaceshim/compiler: Program creation and management
The shims depend on internal APIs at the pinned typescript-go revision. Regenerate them with just shim when their configuration or the upstream APIs change. See tools/gen_shims/README.md.
Local typescript-go adaptations are maintained in the patch stack and applied during just init.
- Native Compilation: Go → machine code
- Parallel Workers: Multi-core utilization
- Zero Conversion: Direct TypeScript AST usage
- Efficient Memory: Streaming diagnostics
- Work Distribution: Files distributed across workers
- Shared Programs: TypeScript programs shared for efficiency
- Memory Streaming: Diagnostics processed immediately
- Version Synchronization: Keep the typescript-go revision, local patches, and generated shims in sync
- Concurrency: Keep mutable rule state local to each rule invocation and use the checker provided by
RuleContext - Performance: Profile changes to program creation, rule execution, and diagnostic reporting on representative projects
- typescript-go - TypeScript compiler in Go
- typescript-eslint - Rule compatibility reference
- Oxlint - Frontend CLI integration