Skip to content

Latest commit

 

History

History
286 lines (199 loc) · 8.21 KB

File metadata and controls

286 lines (199 loc) · 8.21 KB

AGENTS.md - AI Assistant Guide for TSGolint

TSGolint is a static analysis tool, based on typescript-go, for implementing and running type aware linting rules on TypeScript code.

⚠️ CRITICAL: typescript-go Submodule Warning

DO NOT COMMIT SUBMODULE CHANGES WHEN FINALIZING WORK

The typescript-go/ directory is a Git submodule that references Microsoft's TypeScript Go port.

During Development

  • 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

Before Finalizing Work

  • 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

Creating Permanent Changes

If you need to modify typescript-go functionality permanently:

  1. Test your changes locally in the typescript-go directory
  2. Create a patch file in patches/ using git format-patch
  3. Document the patch purpose in patches/README.md
  4. Reset the typescript-go submodule to its original state
  5. The patches are applied during project initialization (just init) using git am --3way --no-gpg-sign ../patches/*.patch

Exposing New Functions

When exposing new functions from typescript-go:

  1. Add the function to the appropriate shim configuration (e.g., shim/*/extra-shim.json)
  2. IMPORTANT: Regenerate the shim files by running:
    just shim
  3. This will expose the new functions for use in TSGolint
  4. Commit both the shim configuration changes and the regenerated shim files

Repository Structure

  • 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 during just init
  • shim/ - [GENERATED - DO NOT EDIT] Generated Go bindings to typescript-go internals
  • cmd/ - CLI entry point and main application
  • internal/ - Core linting logic and rule implementations
    • collections/ - Copied from typescript-go/internal/collections during just init
    • linter/ - Linting engine and worker pool
    • rule/ - Rule interface and context management
    • rules/ - Individual rule implementations (50+ rules)
    • utils/ - Shared utilities and helpers
  • e2e/ - End-to-end tests and fixtures
  • tools/ - Development tools and generators
  • benchmarks/ - Performance benchmarking

Generated Code Warning

The code within ./shim is generated, do not edit it. The code can be re-generated by running just shim.

Key Concepts

1. Architecture Overview

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

2. Performance Design

  • 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

3. Rule Development

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

Common Tasks

Building TSGolint

# Initialize project (first time only)
just init

# Build the binary
just build

Running Tests

# Run all tests (unit + e2e)
just test

# Run unit tests only
go test ./internal/...

# E2E tests with Node.js
cd e2e && pnpm test

Searching Code

Prefer 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/

Adding a New Rule

  1. Create directory: internal/rules/rule_name/
  2. Implement rule following existing patterns
  3. Add tests in rule_name_test.go
  4. Register rule in cmd/tsgolint/main.go
  5. Add fixtures in internal/rules/fixtures/
  6. Create JSON schema for options at internal/rules/rule_name/schema.json
  7. Run node tools/gen-json-schemas.mjs to generate options struct and unmarshaling code
  8. Run just test to verify your implementation

Debugging

Enable debug logging:

OXC_LOG=debug tsgolint

File Modification Guidelines

Safe to Modify

  • internal/rules/* - Rule implementations
  • internal/linter/* - Linting engine logic
  • internal/rule/* - Rule framework
  • cmd/tsgolint/* - CLI implementation
  • e2e/* - End-to-end tests
  • benchmarks/* - Performance tests
  • Documentation files (*.md)

DO NOT Modify

  • typescript-go/* - Submodule in this repo (do not commit direct submodule pointer updates; use patches/* for permanent changes)
  • shim/* - Generated code (regenerate with tools)
  • .gitmodules - Submodule configuration
  • internal/collections/* - Synced from typescript-go by just init

Modify with Caution

  • patches/* - TypeScript-go patches (document thoroughly)
  • tools/gen_shims/* - Shim generator (affects all shims)
  • go.mod - Dependencies (ensure compatibility)

Testing Approach

Rule Testing

Each rule should have:

  • Unit tests with fixtures
  • Valid and invalid code examples
  • Edge cases and type scenarios
  • Fix verification tests

Integration Testing

  • Use e2e/ for full integration tests
  • Test with real TypeScript projects
  • Verify compatibility with typescript-eslint

Performance Considerations

When working on TSGolint:

  • Maintain parallel processing capabilities
  • Avoid blocking operations in workers
  • Use channels for diagnostic streaming
  • Profile changes with benchmarks

Common Pitfalls

  1. Modifying typescript-go without patches: Changes will be lost on submodule update
  2. Editing shims: Manual edits will be overwritten on regeneration
  3. Blocking workers: Reduces parallelism and performance
  4. AST assumptions: TypeScript AST differs from ESTree
  5. Type checker state: Shared across workers, must be thread-safe

Useful Commands

# 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 -l

Integration with Oxlint

TSGolint is designed as a backend for Oxlint:

  1. Oxlint discovers and filters files
  2. Calls TSGolint with file paths
  3. TSGolint returns structured diagnostics
  4. 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).

Contributing Guidelines

  1. Never commit typescript-go submodule pointer changes
  2. Follow existing code patterns
  3. Add comprehensive tests
  4. Document complex logic
  5. Profile performance impacts
  6. Update this guide if needed

Resources