Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
10 KiB
Guide for Copilot
This document provides a concise guide for writing TypeScript fourslash tests and compiler tests, along with build instructions.
Build Instructions Summary
Setup
- Install Node.js (current or LTS)
- Clone the repository:
git clone --depth=1 https://github.com/microsoft/TypeScript - Install dependencies:
npm ci
Common Build Tasks
npx hereby local # Build the compiler into built/local
npx hereby clean # Delete the built compiler
npx hereby tests # Build the test infrastructure
npx hereby runtests # Run all tests
npx hereby runtests-parallel # Run tests in parallel 🚨 MANDATORY BEFORE FINISHING!
npx hereby runtests --runner=fourslash # Run only fourslash tests
npx hereby runtests --runner=compiler # Run only compiler tests
npx hereby runtests --tests=<testPath> # Run specific test
npx hereby baseline-accept # Accept new test baselines
npx hereby lint # Run eslint 🚨 MANDATORY BEFORE FINISHING!
npx hereby format # Run code formatting 🚨 MANDATORY BEFORE FINISHING!
Fourslash Test Syntax Guide
Fourslash tests are interactive TypeScript language service tests. They validate IDE features like completions, quick info, navigation, and refactoring.
Basic Structure
/// <reference path='fourslash.ts'/>
////code goes here with /*markers*/
// Test assertions go here
Key Syntax Elements
1. Source Code Definition
Use //// to define source code lines:
////function foo(x: number) {
//// return x + 1;
////}
////let result = foo(/*marker*/42);
2. Markers for Positioning
Use /**/ for anonymous markers or /*name*/ for named markers:
////let x = /*1*/someValue;
////let y = /*cursor*/anotherValue;
3. Multi-file Tests
Use // @Filename: to define multiple files:
// @Filename: /a.ts
////export const value = 42;
// @Filename: /b.ts
////import { value } from './a';
////console.log(/*marker*/value);
4. Ranges
Use [|text|] to define text ranges:
////function test() {
//// [|return 42;|]
////}
Common API Patterns
Navigation & Positioning
goTo.marker("markerName"); // Navigate to marker
goTo.marker(); // Navigate to anonymous marker /**/
Verification (Prefer these over baselines)
verify.currentLineContentIs("expected content");
verify.completions({ includes: "itemName" });
verify.completions({ excludes: "itemName" });
verify.quickInfoIs("expected info");
verify.codeFix({
description: "Fix description",
newFileContent: "expected content after fix"
});
Completions Testing
verify.completions({
marker: "1",
includes: { name: "foo", source: "/a", hasAction: true },
isNewIdentifierLocation: true,
preferences: { includeCompletionsForModuleExports: true }
});
Code Fixes Testing
verify.codeFix({
description: "Add missing property",
index: 0,
newFileContent: `class C {
property: string;
method() { this.property = "value"; }
}`
});
Formatting
format.document();
verify.currentLineContentIs("formatted content");
Simple Example
/// <reference path='fourslash.ts'/>
////interface User {
//// name: string;
////}
////
////const user: User = {
//// /*completion*/
////};
verify.completions({
marker: "completion",
includes: { name: "name", sortText: "0" }
});
Compiler Test Syntax Guide
Compiler tests validate TypeScript compilation behavior, type checking, and error reporting.
Basic Structure
- Simple
.tsfiles intests/cases/compiler/ - Use comments to indicate expected behavior
- No special test harness - just TypeScript code
Compiler Directives
Use // @directive: value for compiler options:
// @strict: true
// @target: ES2015
// @lib: ES2015,DOM
let x: string = 42; // Error expected
Common Directives
// @strict: true/false
// @noImplicitAny: true/false
// @target: ES5/ES2015/ES2020/ESNext
// @module: commonjs/amd/es6/esnext
// @lib: ES5,DOM/ES2015/ES2020
// @declaration: true/false
// @skipLibCheck: true/false
Multi-file Tests
// @Filename: helper.ts
export function helper(x: number): string {
return x.toString();
}
// @Filename: main.ts
import { helper } from "./helper";
const result = helper(42);
Error Expectations
Use comments to document expected behavior:
abstract class Base {
abstract method(): void;
}
class Derived extends Base {
// Missing implementation - should error
}
new Base(); // Should error - cannot instantiate abstract class
Type Testing Patterns
// Test type inference
let inferred = [1, 2, 3]; // Should infer number[]
// Test type compatibility
type A = { x: number };
type B = { x: number; y: string };
let a: A = { x: 1 };
let b: B = { x: 1, y: "hello" };
a = b; // Should work - B is assignable to A
b = a; // Should error - A missing property y
Simple Example
// Test that optional properties work correctly
interface Config {
required: string;
optional?: number;
}
const config1: Config = { required: "test" }; // Should work
const config2: Config = { required: "test", optional: 42 }; // Should work
const config3: Config = { optional: 42 }; // Should error - missing required
Test Writing Best Practices
For Fourslash Tests
- Prefer validation over baselines - Use
verify.currentLineContentIs()instead ofverify.baseline*() - Use simple, focused examples - Test one feature at a time
- Name markers clearly - Use descriptive marker names like
/*completion*/ - Test the simplest form first - Start with basic cases before complex scenarios
For Compiler Tests
- Use clear file names - Name tests after the feature being tested
- Add explanatory comments - Document expected behavior with comments
- Test error cases - Include both valid and invalid code examples
- Keep tests focused - One primary feature per test file
General Guidelines
- Make tests deterministic - Avoid random or environment-dependent behavior
- Use realistic examples - Test scenarios developers actually encounter
- Start simple - Begin with the most basic case of a feature
- Test edge cases - Include boundary conditions and error scenarios
Running Specific Tests
# Run a specific fourslash test
npx hereby runtests --tests=tests/cases/fourslash/completionForObjectProperty.ts
# Run a specific compiler test
npx hereby runtests --tests=tests/cases/compiler/abstractClassUnionInstantiation.ts
# Run tests matching a pattern
npx hereby runtests --tests=tests/cases/fourslash/completion*.ts
Important Guidelines
🚨 CRITICAL: Before Finishing Your Work 🚨
THESE STEPS ARE MANDATORY BEFORE COMMITTING/PUSHING ANY CHANGES:
- MUST RUN:
npx hereby runtests-parallel(even though it takes 10-15 minutes) - MUST RUN:
npx hereby lintand fix ALL lint issues - MUST RUN:
npx hereby formatas the final step
❌ PRs that fail these checks will be rejected without review.
Keeping Things Tidy
- You can assume lint, tests, and formatting are clean on a fresh clone
- Only run these verification steps AFTER making changes to code
- Run
npx hereby lintand fix ALL issues after making changes - Run
npx hereby formatas your final step after making changes
Test Locations
- Only add testcases in
tests/cases/compilerortests/cases/fourslash - Filenames in
tests/cases/compilermust always end with.ts, not.d.ts - Do not write direct unit tests as they are almost never the correct test format for our repo
Performance Expectations
- Running a set of tests may take up to 4 minutes
- A full test run may take up to 15 minutes
Working with Issues
- Maintainer comments in the issue should generally take priority over OP's comments
- Maintainers might give you hints on where to start. They are not always right, but a good place to start
Debugging Tips
printf debugging is going to be very useful as you are figuring things out.
To do this, use console.log, but you'll need to ts-ignore it.
Write something like this:
function checkSomething(n: Node) {
doSomething(n);
+ // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
+ console.log(`Got node with pos = ${n.pos}`);
doSomethingElse(n);
}
We have a lot of enums so you might want to print back their symbolic name, to do this, index back into the name of the enum
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
console.log(`Got node with kind = ${SyntaxKind[n.kind]}`);
Recommended Workflow
When fixing bugs or implementing features, follow this workflow:
-
Make a testcase that demonstrates the behavior
- Run it (by itself) and review the baselines it generates to ensure it demonstrates the bug
- Add the test and its baselines in one commit
-
Fix the bug by changing code as appropriate
- Put this fix in another commit
-
Run the test you wrote again
- Ensure the baselines change in a way that demonstrates that the bug is fixed
- Put this baseline diff in its own commit
-
Add more testing
- Once you've got the basics figured out, enhance your test to cover edge cases and other variations
- Run the test again and commit the baseline diff along with the test edit
-
🚨 MANDATORY: Run all other tests to ensure you didn't break anything
- REQUIRED: Run
npx hereby runtests-paralleland wait for it to finish (10-15 minutes is normal!) - THIS STEP CANNOT BE SKIPPED - patience is essential!
- Some collateral baseline changes are normal, but review for correctness
- Put these diffs in another commit
- REQUIRED: Run
-
🚨 MANDATORY: Lint and format your changes
- REQUIRED: Run
npx hereby lintand fix ALL issues - REQUIRED: Run
npx hereby formatbefore you're done - YOU CANNOT FINISH WITHOUT THESE STEPS
- Double-check your line endings. Source files in this repo typically use CRLF line endings. Fix all line endings to be consistent before you wrap up
- REQUIRED: Run