Thank you for your interest in contributing to Rslint! Before you start your contribution, please take a moment to read the following guidelines.
For a targeted change, start with Verify a change and prepare only the dependencies and artifacts it needs. For a complete local build:
# Initialize the TypeScript repository (kept at the typescript-go/ path).
git submodule sync -- typescript-go
git submodule update --init --depth 1
pnpm install
go run ./tools/dump_rule_schemas > packages/rslint/rule-schemas.json
pnpm buildInspect git status --short --branch and the branch diff, including staged, unstaged and untracked work. Identify the changed packages and the callers affected by shared API changes, then select the relevant existing commands. The examples below are a menu; run only those needed for the change.
| Check | Existing command with explicit scope |
|---|---|
| One Go rule package | go test ./internal/rules/max_params |
| Related Go packages | go test <changed-package-dir> <affected-consumer-dirs> |
| Catalog registration only | go test ./internal/rules -run '^TestAllContainsEveryGoRuleExactlyOnce$' |
| One JS integration file | CI=true pnpm --dir packages/rslint-test-tools exec rs test run tests/eslint/rules/max-params.test.ts |
| One Rust crate | cargo test -p tsgo-client |
| Go lint for a changed package | golangci-lint run --new-from-merge-base=origin/main ./internal/rules/max_params |
| Format changed JS/TS/docs | pnpm exec rs fmt <changed-files> |
| Format changed Go files | gofmt -w <changed-go-files> |
| Spell-check changed text | pnpm run check-spell <changed-text-files> |
An aggregate package can run more than its name suggests: unfiltered go test ./internal/rules also runs all-rule compiler compatibility and heritage suites. For registration-only changes, keep the named test selection above; select other tests only when their behavior is affected.
Explicit rs fmt paths still obey rstack.config.mts exclusions, including rule Markdown under internal/**/rules/**/*.md. Select supported, non-ignored changed files and skip the command when none remain; an ignored-only selection fails instead of formatting those files.
Run Rstest verification with CI=true so missing snapshots fail instead of being created automatically. The example uses POSIX shell syntax; set the equivalent environment variable in other shells. Use -u only for intentional snapshot generation and review the result against the expected behavior.
Before JS integration tests exercise changed Go code, run pnpm --filter @rslint/core build:bin. Build affected JS artifacts with the workspace's existing build command when its source changed or the required output is missing. Go test-only changes need the owning package's tests. Shared helpers need their affected consumers; package imports are a starting point for tracing the changed API, not a reason to run every rule in a plugin.
Root test:go and lint:go already include ./cmd/... ./internal/...; appending another directory adds to that scope. Use go test and golangci-lint run directly for selected packages. Root pnpm test also runs multiple workspaces. Full-suite verification follows an explicit user/reviewer request; CI's command list is not a local checklist.
Keep the passing commands/results in the task's existing progress record. Reuse them while their relevant inputs remain unchanged. Report platform, generated-content or cross-language gaps with the additional targeted checks they need. Documentation-only changes require no language tests unless executable examples, generated content, builds or runtime behavior are affected.
Branch naming and test organization follow AGENTS.md. Before delivery, check the current branch with git branch --show-current, review the upstream/extras split and verify new JS integration files are included in packages/rslint-test-tools/rstack.config.mts. Before committing, ensure pnpm run check-spell <changed-text-files> and pnpm run format:check have passed after the final relevant edit, reusing valid results. Spell-check file arguments also cover changed hidden paths such as .agents/; the default globs omit hidden directories.
The existing pre-commit hook runs rs staged. Install it through pnpm run prepare; git config --get core.hooksPath should point to .rstack/hooks/_. If a checkout still points to old Husky hooks, inspect them before migrating once with pnpm exec rs hooks --force. Keep ordinary prepare non-forcing.
The typescript-go/ submodule tracks microsoft/TypeScript. The Go compiler lives under typescript-go/tsc/, and its JS AST API lives under typescript-go/packages/typescript/. Existing checkouts must run git submodule sync -- typescript-go before updating the submodule to pick up the new remote URL.
The migration pins the latest main-branch commit checked on 2026-09-05:
| Reference | Commit |
|---|---|
| Selected TypeScript main | 1f70213d4922b434345f639b441681e470c7cfc1 |
Latest stable release, v7.0.2 |
1e4744d68260a7cb91b62b12edc3f6a2187faaf1 |
| Previous standalone compiler | 01cbcdd8643cfa17cc8156b60849559c56324601 |
| Corresponding imported history | 3e5f89624cdd03a69d6c5bb92d3a352195ad3a41 |
The old commit hash is absent from the new repository because its history was rewritten. Its root tree exactly matches the imported commit's tsc/ tree (23a5bf0c01e3f6dc1d4c0caf7aaef69ba9d9de64). However, that historical commit retains tsc/_submodules/TypeScript without a root .gitmodules mapping. Recursive Git operations, including the authentication setup in our actions/checkout version, fail on it. The stable tag also predates the final repository layout. The selected main commit has the completed layout and removes this obsolete nested submodule.
The Go compiler module and all shim module/import/linkname paths use github.com/microsoft/TypeScript/tsc. go.work resolves the compiler to typescript-go/tsc. Both @rslint/api and the Go encoder use this same pinned checkout so that AST layouts and enum values stay aligned.
The JS declaration build uses TypeScript 6.0.3, matching upstream's build dependency. TypeScript 5.9.3 rejects the new generator-backed API method types. The AST declarations now reference both the synchronous and asynchronous APIs, which are included in packages/rslint-api/tsconfig.build.json.
Compiler compatibility changes include the lazy SourceFile.HasIdentifier cache, type-reference nodes for interface extends and class implements, and new compiler-host, resolver, and LSP client interfaces. Rules retain their existing ESLint diagnostics and fix boundaries despite the new node kinds. On macOS, upstream's Realpath now preserves path casing; config discovery verifies filesystem identity before coalescing native case aliases. The shim generator also mirrors private generic checker stores and validates their memory layout.
To select a future upstream revision and refresh its integration:
git submodule sync -- typescript-go
git -C typescript-go fetch --depth 1 origin <commit-or-tag>
git -C typescript-go checkout --detach FETCH_HEAD
bash tools/update-typescript-go.shThe helper resolves the exact submodule commit to a Go pseudo-version, updates the root and shim module requirements, regenerates shims from the local checkout, tidies those modules, builds both Go entrypoints, and checks the unsafe checker mirror's field layout. Review the resulting changes and run the Go and JS tests before committing the new submodule revision. Compiler API changes may require adapting the shim declarations and their consumers.
After bumping to a new stable version and before publishing, run the release sync command separately:
git fetch origin --tags
pnpm sync:version-infoThis replaces pnpm sync:rule-releases. It updates website/releases.json with
new rules and the pinned TypeScript commit for the new stable release. If the
package version matches the latest stable tag, the command skips it to preserve
the published record. Stage any submodule revision change first. An exact upstream release tag supplies
typescript.releaseVersion; otherwise the value is null. A failed upstream
lookup stops the command without writing the file.
Commit the generated JSON with the release changes. The website uses it for rule
version badges and the TypeScript compiler version table. The table tracks releases
from 0.9.2 onward; earlier versions retain only their existing rule history.
pnpm sync:version-info full rebuilds rule history from stable tags while preserving
recorded compiler bindings. Fetch all tags before running it.
The scripts live in scripts/sync-version-info/: index.js is the entry point,
release.js maintains rule release history, and version.js resolves the
TypeScript commit and release version.
After building, you can test the rslint CLI:
# Test the binary
./packages/rslint/bin/rslint.js --help
# Lint the project itself
./packages/rslint/bin/rslint.jsTo Debug the VSCode Extension:
- Setup launch configuration
cp .vscode/launch.template.json .vscode/launch.json- Start debugging
- Open the Command Palette (
Cmd+Shift+P) - Run
Debug: Start Debuggingor pressF5 - Alternatively, go to the
Run and Debugsidebar and selectRun Extension