TSGolint is a static analysis tool, based on typescript-go, for implementing and running type aware linting rules on TypeScript code.
DO NOT COMMIT SUBMODULE CHANGES WHEN FINALIZING WORK
The typescript-go/ directory is a Git submodule that references Microsoft's TypeScript Go port.
- Changes ARE allowed to the typescript-go submodule for testing and development
- You can freely modify and commit within the
typescript-go/folder - Use normal git commands within the typescript-go directory as needed
- Convert any typescript-go changes into patch files in the
patches/directory - NEVER commit the submodule pointer changes to the TSGolint repository
- When you see
modified: typescript-go (new commits)in git status, do NOT stage/commit this change
If you need to modify typescript-go functionality permanently:
- Test your changes locally in the typescript-go directory
- Create a patch file in
patches/usinggit format-patch - Document the patch purpose in
patches/README.md - Reset the typescript-go submodule to its original state
- The patches are applied during project initialization (
just init) usinggit am --3way --no-gpg-sign ../patches/*.patch
When exposing new functions from typescript-go:
- Add the function to the appropriate shim configuration (e.g.,
shim/*/extra-shim.json) - IMPORTANT: Regenerate the shim files by running:
just shim
- This will expose the new functions for use in TSGolint
- Commit both the shim configuration changes and the regenerated shim files
typescript-go/- [SUBMODULE] TypeScript Go port submodule (temporary local edits are OK for testing; never commit submodule pointer changes)patches/- Patches applied to typescript-go duringjust initshim/- [GENERATED - DO NOT EDIT] Generated Go bindings to typescript-go internalscmd/- CLI entry point and main applicationinternal/- Core linting logic and rule implementationscollections/- Copied fromtypescript-go/internal/collectionsduringjust initlinter/- Linting engine and worker poolrule/- Rule interface and context managementrules/- Individual rule implementations (50+ rules)utils/- Shared utilities and helpers
e2e/- End-to-end tests and fixturestools/- Development tools and generatorsbenchmarks/- Performance benchmarking
The code within ./shim is generated, do not edit it. The code can be re-generated by running just shim.
TSGolint serves as a type-aware linting backend for Oxlint:
- Oxlint handles CLI, file discovery, and output formatting
- TSGolint processes TypeScript files and returns diagnostics
- Uses typescript-go for native-speed parsing and type checking
- Implements 50+ type-aware rules from typescript-eslint
- Parallel Processing: Uses all CPU cores with worker pool pattern
- Direct AST Usage: No TypeScript → ESTree conversion overhead
- Native Speed: Go implementation for 20-40x speedup over ESLint
Rules follow a consistent pattern:
type Rule struct {
Name string
Run func(ctx RuleContext, options any) RuleListeners
}Each rule:
- Registers listeners for specific AST node types
- Uses TypeScript checker for type-aware analysis
- Returns diagnostics with source locations and fixes
# Initialize project (first time only)
just init
# Build the binary
just build# Run all tests (unit + e2e)
just test
# Run unit tests only
go test ./internal/...
# E2E tests with Node.js
cd e2e && pnpm testPrefer ast-grep over grep/ripgrep for searching code patterns:
# Find all rule definitions
ast-grep --pattern 'var $RULE = rule.Rule{$$$}' --lang go internal/rules/
# Find rules that parse options
ast-grep --pattern 'utils.UnmarshalOptions[$TYPE](options, $RULE_NAME)' --lang go internal/rules/
# Find TypeScript test fixtures
ast-grep --pattern 'expect($$$)' --lang ts internal/rules/fixtures/- Create directory:
internal/rules/rule_name/ - Implement rule following existing patterns
- Add tests in
rule_name_test.go - Register rule in
cmd/tsgolint/main.go - Add fixtures in
internal/rules/fixtures/ - Create JSON schema for options at
internal/rules/rule_name/schema.json - Run
node tools/gen-json-schemas.mjsto generate options struct and unmarshaling code - Run
just testto verify your implementation
Enable debug logging:
OXC_LOG=debug tsgolintinternal/rules/*- Rule implementationsinternal/linter/*- Linting engine logicinternal/rule/*- Rule frameworkcmd/tsgolint/*- CLI implementatione2e/*- End-to-end testsbenchmarks/*- Performance tests- Documentation files (*.md)
typescript-go/*- Submodule in this repo (do not commit direct submodule pointer updates; usepatches/*for permanent changes)shim/*- Generated code (regenerate with tools).gitmodules- Submodule configurationinternal/collections/*- Synced fromtypescript-gobyjust init
patches/*- TypeScript-go patches (document thoroughly)tools/gen_shims/*- Shim generator (affects all shims)go.mod- Dependencies (ensure compatibility)
Each rule should have:
- Unit tests with fixtures
- Valid and invalid code examples
- Edge cases and type scenarios
- Fix verification tests
- Use
e2e/for full integration tests - Test with real TypeScript projects
- Verify compatibility with typescript-eslint
When working on TSGolint:
- Maintain parallel processing capabilities
- Avoid blocking operations in workers
- Use channels for diagnostic streaming
- Profile changes with benchmarks
- Modifying typescript-go without patches: Changes will be lost on submodule update
- Editing shims: Manual edits will be overwritten on regeneration
- Blocking workers: Reduces parallelism and performance
- AST assumptions: TypeScript AST differs from ESTree
- Type checker state: Shared across workers, must be thread-safe
# Initialize project and apply patches
just init
# Build the project
just build
# Run all tests
just test
# Format code
just fmt
# Run linter
just lint
# Full check (format, lint, test)
just ready
# Regenerate shims
just shim
# Update typescript-go from upstream
just pull
# Run specific rule tests
go test ./internal/rules/rule_name/...
# Update all rule output snapshots
just update-snaps
# Profile performance
tsgolint -cpuprof cpu.prof <files>
go tool pprof cpu.prof
# Search for code patterns (prefer ast-grep over grep/ripgrep for code searches)
ast-grep --pattern 'var $RULE = rule.Rule{$$$}' --lang go internal/rules/
# Check rule coverage
ast-grep --pattern 'var $RULE = rule.Rule{$$$}' --lang go internal/rules/ | wc -lTSGolint is designed as a backend for Oxlint:
- Oxlint discovers and filters files
- Calls TSGolint with file paths
- TSGolint returns structured diagnostics
- Oxlint formats and displays results
The integration point is the headless mode in cmd/tsgolint/headless.go (with payload schema in cmd/tsgolint/payload.go).
- Never commit
typescript-gosubmodule pointer changes - Follow existing code patterns
- Add comprehensive tests
- Document complex logic
- Profile performance impacts
- Update this guide if needed
- TypeScript-ESLint Rules - Rule specifications
- typescript-go - TypeScript Go port
- Oxlint - Frontend linter
- Architecture Doc - Detailed system design