compiler-and-fourslash-tests
Skill26k starsChanged 9 months agoApache-2.0Archived repositoryView on GitHub
How to write, run, and debug compiler tests and fourslash (LSP) tests in the typescript-go repository. Covers test file formats, directives, markers, baseline management, and the fourslash verification API.
We split this skill into the rules it gives an agent
20 rules. About 0 are common, 5 are distinctive, and 15 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 52
Use // @filename: directives to define multiple files in one test:
- Very specific: line 89
Use // @symlink: to create symlinks in the virtual filesystem:
- Very specific: line 104
Always use npx hereby test to run tests.
- Very specific: line 112
If a test fails, the output will include the full test name and package, which you can use to re-run it directly with go test for debugging …
- Very specific: line 116
Use go test directly only when you need verbose output for a specific test to debug with print statements.
- Very specific: line 199
Create a Go test file in internal/fourslash/tests/.
- Very specific: line 300
Use // @Filename: (capital F) to define multiple files:
- Distinctive: line 355
Import the test utilities for shared constants:
- Very specific: line 446
Use fourslash.SkipIfFailing(t) for tests that don't pass yet
- Distinctive: line 447
Should not be manually edited (they'll be overwritten on regeneration)
- Very specific: line 453
Must always pass (no SkipIfFailing)
- Distinctive: line 454
Test specific behaviors, edge cases, or new features
- Very specific: line 473
Create testdata/tests/cases/compiler/myTest.ts with test code and directives
- Very specific: line 474
Run all tests: npx hereby test
- Very specific: line 479
Create internal/fourslash/tests/myTest_test.go with the test function
- Very specific: line 486
If a test fails, use the package and test name from the output to re-run with verbose output: go test ./internal/testrunner/ -run …
- Very specific: line 487
Check baseline diffs: git diff --diff-filter=AM --no-index ./testdata/baselines/reference ./testdata/baselines/local
- Very specific: line 488
If the new output is correct, run npx hereby test again, then accept: npx hereby baseline-accept
- Distinctive: line 489
If not, fix the code and re-run
- Distinctive: line 492
If a test panics without a clear stack trace, run all tests in the package sequentially with verbose mode to identify which test caused the …
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Writing, Running, and Debugging Compiler Tests and Fourslash Tests
This guide covers the complete testing workflow for the typescript-go repository, including compiler tests (type-checking, emit, diagnostics) and fourslash tests (language server features like completions, hover, go-to-definition).
1. Compiler Tests
Compiler tests validate the TypeScript compiler's behavior: diagnostics, JavaScript emit, source maps, type/symbol baselines, and more. Each test is a .ts or .tsx file that the test runner compiles, then compares output against stored baselines.
1.1 Where Test Files Live
| Path | Purpose |
testdata/tests/cases/compiler/ | Regression tests (local to this repo) |
testdata/tests/cases/conformance/ | Conformance tests (local to this repo) |
_submodules/TypeScript/tests/cases/compiler/ | Submodule tests from upstream TypeScript |
_submodules/TypeScript/tests/cases/conformance/ | Submodule conformance tests from upstream |
1.2 Writing a New Compiler Test
A compiler test is just a .ts or .tsx file — no Go code needed. Place it in testdata/tests/cases/compiler/ for regression tests or testdata/tests/cases/conformance/<subdir>/ for conformance tests.
Simple single-file test
// testdata/tests/cases/compiler/myNewTest.ts
const x: number = "hello"; // expect type errorUsing compiler option directives
Set compiler options with // @option: value comment directives at the top of the file:
// @target: es2020
// @strict: true
// @declaration: true
// @jsx: react
// @noEmit: true
const x: number = 42;Multi-file test
Use // @filename: directives to define multiple files in one test:
// @target: es2015
// @module: commonjs
// @filename: /src/utils.ts
export function greet(name: string): string {
return `Hello, ${name}`;
}
// @filename: /src/main.ts
import { greet } from "./utils";
const msg: number = greet("world"); // type errorGenerating test variations
Options can specify multiple comma-separated values to generate separate sub-test configurations:
// @target: es2015, esnext
// @module: commonjs, esnext
// @strict: true, false
export const x = 1;This generates a sub-test for each combination, with names like myTest.ts (target=es2015,module=commonjs,strict=true).
Note: // @lib: is not variant — commas add additional lib files rather than creating separate test configurations:
// @lib: es2020,domSymlink tests
Use // @symlink: to create symlinks in the virtual filesystem:
// @symlink: /src -> /node_modules/mylibOther directives
// @currentDirectory: /custom/path— Set the working directory// @noImplicitReferences— Don't auto-include referenced files
1.3 Running Compiler Tests
Via hereby (recommended)
Always use npx hereby test to run tests. It ensures a clean state by clearing stale baselines before running, so results are always trustworthy. Trust the results — if hereby test passes, the tests pass.
It's generally best to run all tests — the full suite is very quick and will find issues you didn't realize you were introducing:
npx hereby test # Run ALL tests — recommended, fast, and catches unexpected breakageIf a test fails, the output will include the full test name and package, which you can use to re-run it directly with go test for debugging (see below).
Via Go directly (for print-debugging a single test)
Use go test directly only when you need verbose output for a specific test to debug with print statements. The test output from hereby test will tell you the exact package and test name to use:
go test ./internal/testrunner/ -run 'TestLocal/myNewTest' -vThe test entry points are:
TestLocal— runs tests fromtestdata/tests/cases/(bothcompiler/andconformance/)TestSubmodule— runs tests from_submodules/TypeScript/tests/cases/and generates diff baselines
What happens during a test run
For each test file, the runner:
- Parses directives (
// @option:,// @filename:, etc.) - Generates configurations for each option variation
- For each configuration, runs these parallel sub-tests:
error— Verifies diagnostics against.errors.txtbaselineoutput— Verifies JavaScript emit against.jsbaselinesourcemap— Verifies source map outputsourcemap record— Verifies source map recordunion ordering— Validates AST union type orderingsource file parent pointers— Validates AST structure integrity
1.4 Baseline System
Baselines are the expected output files that test results are compared against.
| Directory | Purpose |
testdata/baselines/reference/ | Golden/expected baselines (committed to repo) |
testdata/baselines/local/ | Generated during test runs (not committed) |
Baseline file types
| Extension | Content |
.errors.txt | Diagnostic error messages |
.js | Emitted JavaScript |
.d.ts | Declaration output |
.symbols | Symbol information |
.types | Type information |
.sourcemap.txt | Source map output |
.trace.json | Trace output |
Viewing baseline diffs
git diff --diff-filter=AM --no-index ./testdata/baselines/reference ./testdata/baselines/localAccepting baselines
Important: Only accept baselines immediately after a successful npx hereby test run. The hereby test command clears stale baselines before running, so accepting after it guarantees you're only accepting baselines from the current test run. If you accept without running hereby test first, you risk accepting old/stale baselines from previous runs.
npx hereby test # MUST run this first — clears stale state
npx hereby baseline-accept # Then accept the baselinesThe baseline-accept task:
- Copies all files from
local/toreference/(excluding.deletefiles) - Deletes reference files that have corresponding
.deletemarkers inlocal/
2. Fourslash Tests
Fourslash tests validate language server (LSP) features: completions, hover/quick info, go-to-definition, find references, rename, code fixes, formatting, and more. They're Go test files that set up TypeScript source with position markers, then verify LSP responses.
2.1 Where Test Files Live
| Path | Purpose |
internal/fourslash/tests/*.go | Hand-written fourslash tests |
internal/fourslash/tests/gen/*.go | Auto-generated from upstream TypeScript fourslash tests |
internal/fourslash/tests/manual/*.go | gen tests migrated to manual with npm run makemanual |
internal/fourslash/ | Test harness and utilities |
internal/fourslash/tests/util/ | Shared test constants (DefaultCommitCharacters, etc.) |
Key difference: Generated tests in gen/ use fourslash.SkipIfFailing(t) for tests that are known to not yet work. Hand-written tests should always pass. Tests in manual/ are generated tests that have been migrated and possibly modified — they should not be created from scratch.
2.2 Writing a New Fourslash Test
Create a Go test file in internal/fourslash/tests/. The file uses the fourslash_test package.
Minimal template
package fourslash_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)
func TestMyFeature(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `
var x/*marker1*/ = 42;
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "marker1", "var x: number", "")
}Real-world example: Quick Info
func TestBasicQuickInfo(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `
/**
* Some var
*/
var someVar/*1*/ = 123;
/**
* Other var
* See {@link someVar}
*/
var otherVar/*2*/ = someVar;
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "1", "var someVar: number", "Some var")
f.VerifyQuickInfoAt(t, "2", "var otherVar: number",
"Other var\nSee [someVar](file:///basicQuickInfo.ts#4,5-4,12)")
}Real-world example: Editing and Completions
func TestBasicEdit(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `export {};
interface Point {
x: number;
y: number;
}
declare const p: Point;
p/*a*/`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.GoToMarker(t, "a")
f.Insert(t, ".")
f.GoToEOF(t)
f.VerifyCompletions(t, nil, &fourslash.CompletionsExpectedList{
IsIncomplete: false,
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
},
Items: &fourslash.CompletionsExpectedItems{
Exact: []fourslash.CompletionsExpectedItem{
&lsproto.CompletionItem{
Label: "x",
Kind: new(lsproto.CompletionItemKindField),
SortText: new(string(ls.SortTextLocationPriority)),
},
"y",
},
},
})
}Marker syntax
Markers define cursor positions and text ranges in the test content:
| Syntax | Description | Example | ||||
/*name*/ | Named position marker | var x/*pos*/ = 1; | ||||
/*1*/, /*2*/ | Numbered markers | foo(/*1*/, /*2*/) | ||||
| `[ | text | ]` | Range marker (selects text) | `[ | let x: number | ]` |
Multi-file tests
Use // @Filename: (capital F) to define multiple files:
const content = `
// @Filename: /src/utils.ts
export function greet(name: string) { return name; }
// @Filename: /src/main.ts
import { greet } from "./utils";
greet(/*marker*/"world");
`Setting compiler options
Embed a tsconfig.json file or use directive comments:
const content = `
// @Filename: /tsconfig.json
{ "compilerOptions": { "strict": true, "target": "es2020" } }
// @Filename: /src/test.ts
const x/*1*/ = 42;
`2.3 Verification Methods (Common API)
The fourslash.FourslashTest type (variable f) provides these verification methods:
Quick Info / Hover
f.VerifyQuickInfoAt(t, "marker", "var x: number", "documentation text")
f.VerifyBaselineHover(t) // generates baseline fileCompletions
f.VerifyCompletions(t, "marker", &fourslash.CompletionsExpectedList{
IsIncomplete: false,
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{
Includes: []fourslash.CompletionsExpectedItem{
&lsproto.CompletionItem{Label: "myVar"},
},
// Or use Exact for exact match:
// Exact: []fourslash.CompletionsExpectedItem{"x", "y"},
},
})Import the test utilities for shared constants:
import . "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
// Provides: DefaultCommitCharacters, Ignored, CompletionGlobalThisItem, etc.Navigation
f.VerifyBaselineGoToDefinition(t) // baseline-based
f.VerifyBaselineGoToTypeDefinition(t)
f.VerifyBaselineGoToImplementation(t)References and Rename
f.VerifyBaselineFindAllReferences(t)
f.VerifyBaselineRename(t)Diagnostics
f.VerifyNoErrors(t)
f.VerifyErrorExistsBetweenMarkers(t, "start", "end")
f.VerifyBaselineNonSuggestionDiagnostics(t)Signature Help
f.VerifyBaselineSignatureHelp(t)
f.VerifyNoSignatureHelp(t)Editing (simulating user actions)
f.GoToMarker(t, "marker") // move cursor to marker position
f.Insert(t, ".") // type text at cursor
f.Backspace(t, 3) // delete 3 characters before cursor
f.DeleteAtCaret(t, 5) // delete 5 characters after cursor
f.Paste(t, "new text") // paste text
f.Replace(t, start, len, "replacement")
f.GoToEOF(t) // move to end of file
f.GoToFile(t, "/src/main.ts") // switch to another fileOther LSP Features
f.VerifyBaselineDocumentHighlights(t)
f.VerifyBaselineDocumentSymbol(t)
f.VerifyBaselineCallHierarchy(t)
f.VerifyBaselineInlayHints(t)
f.VerifyBaselineSelectionRanges(t)
f.VerifyBaselineClosingTags(t)
f.FormatDocument(t, "/test.ts")
f.VerifyOrganizeImports(t, expectedContent, actionKind, prefs)2.4 Running Fourslash Tests
# Run ALL tests (recommended — fast, ensures clean state, catches unexpected breakage)
npx hereby test
# For print-debugging a specific test with verbose output
go test ./internal/fourslash/tests -run TestBasicQuickInfo -v2.5 Fourslash Baselines
Fourslash tests that use VerifyBaseline* methods generate baselines under:
testdata/baselines/reference/fourslash/<command>/Where <command> is one of: quickInfo, signatureHelp, goToDefinition, goToType, goToImplementation, findAllReferences, documentHighlights, findRenameLocations, callHierarchy, Code Lenses, Document Symbols, Inlay Hints, etc.
File extensions vary by command:
.baseline— quickInfo, signatureHelp, diagnostics, etc..baseline.jsonc— most other features.baseline.md— auto imports.callHierarchy.txt— call hierarchy
Accept baselines the same way as compiler tests — but only after running npx hereby test:
npx hereby test # MUST run first to clear stale baselines
npx hereby baseline-accept2.6 Generated vs. Hand-Written Tests
Generated tests (in gen/) are auto-converted from the upstream TypeScript fourslash test suite using the script at internal/fourslash/_scripts/convertFourslash.mts. They:
- Use
fourslash.SkipIfFailing(t)for tests that don't pass yet - Should not be manually edited (they'll be overwritten on regeneration)
- Provide coverage for ported TypeScript behavior
Tests in manual/ are gen tests that have been migrated with npm run makemanual. They should not be created from scratch — only use makemanual to move a generated test that needs modification.
Hand-written tests (directly in internal/fourslash/tests/):
- Must always pass (no
SkipIfFailing) - Test specific behaviors, edge cases, or new features
- Are the right place for custom regression tests
3. General Testing Practices
3.1 Key hereby Commands
| Command | Description |
npx hereby test | Run all tests (recommended — fast, clears stale state) |
npx hereby baseline-accept | Accept local baselines as new reference |
npx hereby format | Format code (uses dprint) |
npx hereby lint | Run linters (uses golangci-lint) |
3.2 Typical Workflow
Adding a new compiler test
- Create
testdata/tests/cases/compiler/myTest.tswith test code and directives - Run all tests:
npx hereby test - Review generated baselines:
git diff --diff-filter=AM --no-index ./testdata/baselines/reference ./testdata/baselines/local - Accept (only after
hereby test):npx hereby baseline-accept
Adding a new fourslash test
- Create
internal/fourslash/tests/myTest_test.gowith the test function - Run all tests:
npx hereby test - Review any generated baselines:
git diff --diff-filter=AM --no-index ./testdata/baselines/reference ./testdata/baselines/local - Accept (only after
hereby test):npx hereby baseline-accept
Investigating a test failure
- Run all tests:
npx hereby test - If a test fails, use the package and test name from the output to re-run with verbose output:
go test ./internal/testrunner/ -run 'TestLocal/failingTest' -v - Check baseline diffs:
git diff --diff-filter=AM --no-index ./testdata/baselines/reference ./testdata/baselines/local - If the new output is correct, run
npx hereby testagain, then accept:npx hereby baseline-accept - If not, fix the code and re-run
Debugging an unrecovered panic
If a test panics without a clear stack trace, run all tests in the package sequentially with verbose mode to identify which test caused the panic:
go test ./internal/testrunner/ -parallel=1 -vThe last test that shows as running before the panic output is the one that caused it.
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- microsoft/TypeScript★ 112k90% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repositoryOne other file it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

