- NEVER run the vite dev server or anything that listens on any port yourself! Ask the user to do it!
- Read
docs/explainer.mdfor an overview of the project if you need it - The
design/documents (includingdesign/DESIGN.mdanddesign/SPEC_DIFFERENCES.md) are OUTDATED and are NOT authoritative. They describe an earlier state of the compiler and runtime and have drifted from the actual behavior. Treat the compiler source, thegs/runtime overrides, and the passing compliance tests undertests/tests/as the source of truth. When you touch an area a design doc describes, update that doc to match reality (or note the divergence) rather than implementing to the stale text. They need a full review and update.
- DO NOT maintain backwards compatibility - this is an experimental project
- Remove any "for backwards compatibility" comments and fallback logic
- NEVER hardcode things: examples include function names, builtins, etc.
- Actively try to improve the codebase to conform to these rules when the opportunity arises
- Go standard library sources are located at "go env GOROOT" (shell command)
- Leverage adding more tests (e.g.,
compiler/analysis_test.go) instead of debug logging for diagnosing issues. If the new test case is temporary, add atmp_test.gofile to keep things separated. - AVOID type arguments unless necessary (prefer type inference)
- When making Git commits referencing issues use the short form: Fixes #128 (for example)
- When making Git commits use the existing commit message pattern and Linux-kernel style commit message bodies.
- When you would normally add a new compliance test check if a very-similar compliance test already exists and if so extend that one instead. For example testing another function in the same package.
GoScript is an experimental Go to TypeScript transpiler that enables developers to convert high-level Go code into maintainable TypeScript. It translates Go constructs—such as structs, functions, and pointer semantics—into idiomatic TypeScript code while preserving Go's value semantics and type safety. It is designed to bridge the gap between the robust type system of Go and the flexible ecosystem of TypeScript.
This is an experimental project - we do not maintain backwards compatibility and prioritize simplicity and correctness over legacy support. You may sometimes encounter a problem that requires a complete re-design or re-think or re-architecting of an aspect of goscript, which is perfectly okay, in this case write a design to tests/WIP.md and think it through extensively before performing your refactor. It's perfectly OK to delete large swaths of code as needed. Focus on correctness.
If you want to overwrite WIP.md you must rm it first.
The GoScript runtime, located in gs/builtin/builtin.ts, provides necessary helper functions and is imported in generated code using the @goscript/builtin alias.
Output Style: Generated TypeScript should not use semicolons and should always focus on code clarity and correctness.
Philosophy: Follow Rick Rubin's concept of being both an engineer and a reducer (not always a producer) by focusing on the shortest, most straightforward solution that is correct.
When working on compliance tests:
-
Test Location: Compliance tests are located at
./tests/tests/{testname}/testname.gowith a package main and usingprintln()only for output, trying to not import anything. -
Running Tests:
For a specific test:
go test -timeout 60s -run ^TestCompliance/if_statement$ ./compilerFor the full suite (RECOMMENDED approach for detecting failures):
# Run once, capture to file, check result mkdir -p .tmp && go test -timeout 10m ./compiler 2>&1 > .tmp/test_output.txt; echo "Exit code: $?" # If exit code is non-zero, find all failing tests: grep -E "^--- FAIL:" .tmp/test_output.txt # Then run specific failing tests with -v for details: go test -v -timeout 60s -run ^TestCompliance/failing_test_name$ ./compiler
IMPORTANT: Do NOT pipe test output directly to grep/tail during the test run. The test framework may produce verbose output that looks like errors but isn't. Always check the exit code first, then analyze the output file if needed. The
.tmp/directory is gitignored. -
Analysis Process:
- Run the compliance test to check if it passes
- If not, review the output to see why
- Deeply consider the generated TypeScript from the source Go code
- Think about what the correct TypeScript output would look like with as minimal of a change as possible
- If the test is too complex (many cascading errors, large dependencies, or unclear root cause):
- Create a new, simpler compliance test that isolates a specific subset of the problem
- Name it descriptively (e.g.,
method_async_callfor async method invocation issues) - Focus on reproducing just one aspect of the failure in minimal code
- Fix the isolated test first, then return to the original complex test
-
Implementation Workflow:
- Review the code under
compiler/*.goto determine what needs to be changed - Write your analysis and info about the task at hand to
tests/WIP.md(overwrite any existing contents) - Apply the planned changes to the
compiler/code - Run the integration test again
- Repeat: update compiler code and/or
tests/WIP.mduntil the compliance test passes successfully - If you make two or more edits and the test still does not pass, ask the user how to proceed providing several options
- After fixing a specific test, re-run the full compliance test to verify everything works properly
- Review the code under
Once the issue is fixed and the compliance test passes you may delete WIP.md without updating it with a final summary.
NOTE: ./tests/deps/ contains library dependencies compiled by the goscript compiler! do not edit! they will be re-generated when running the tests.
-
Follow Existing Patterns: Always reference
/design/DESIGN.mdfor design details and existing code patterns -
Function Naming Convention: When writing functions that convert Go AST to TypeScript, name them to match the AST type:
- For
*ast.FuncDecl, useWriteFuncDecl - Try to make a 1-1 match between AST type and function name
- Avoid hiding logic in unexported functions
- For
-
Implementation Completeness:
- Avoid leaving undecided implementation details in the code
- Make a decision and add a comment explaining the choice if necessary
-
Struct Field Policy:
- You may not add new fields to
GoToTSCompiler - You may add new fields to
Analysisif you are adding ahead-of-time analysis only
- You may not add new fields to
When working with golangci-lint:
- Running the Linter: Use
bun lintto run the linter,bun lint:gofor go andbun lint:jsfor js - Fixing Errors: Address linter errors in the affected code files
- Iterating: Repeat the linting process until no errors remain
- Ignoring Warnings: You can ignore linter errors with inline comments when the warning is unnecessarily strict:
defer f.Close() //nolint:errcheck
Make sure that bun test and bun lint both pass before suggesting a task is complete.
When squashing commits on the wip branch:
- Verify we are on the
wipbranch; if not, ask the user what to do - Note the current branch name and HEAD commit hash
- Verify the git worktree is clean; if not, ask the user what to do
- Check out
origin/masterwith--detach - Run
git merge --squash COMMIT_HASHwhere COMMIT_HASH is the noted hash - Ask the user if we are done or if we should merge this to master
- If merging to master:
git checkout masterthengit cherry-pick HEAD@{1}
When updating design documentation from integration tests:
- Read
design/DESIGN.mdfor the initial state - List available tests with
ls ./tests/tests/*.gs.ts(each .gs.ts corresponds to a .go file) - Read the .go and .gs.ts files
- Update
design/DESIGN.mdwith any previously undocumented behavior from the tests - Skip integration tests that are obviously already represented in the design
When updating design documents:
- Receive instructions from the user on design changes
- Consult the Go specification at
design/GO_SPECIFICATION.htmlas needed - Update
design/DESIGN.mdwith the finalized design changes - Use
design/WIP.mdfor work-in-progress notes or drafts if necessary - Ensure updates accurately reflect the user's instructions and align with project goals
- Note any divergences from the Go specification clearly
- Follow any already-noted divergences carefully
When eliminating dead code if requested by the user:
- Receive instructions from the user
- Run
golangci-lint run --no-config --enable-only=unused(exactly this command) - Remove any unused code in
./compilerignoring ./tests - Any line which is unused in
./testsadd a//nolint:unusedcomment at the end. - Rerun the golangci-lint command to ensure we got everything.
The playground at website/ compiles Go to TypeScript using a WASM build of the compiler. It only supports single-file compilation without external dependencies.
- Playground examples are defined in
scripts/generate-examples.ts(theCURATED_EXAMPLESarray) - Do NOT add examples that import packages beyond
@goscript/builtin(e.g.,encoding/json) until dependency bundling is implemented - Run
bun run scripts/generate-examples.tsthencd website && bun run buildto update